first commit

This commit is contained in:
CHIEFSOFT\ameye
2023-11-30 13:20:54 -05:00
commit e9e5c0546c
5833 changed files with 1801865 additions and 0 deletions
@@ -0,0 +1,185 @@
<?php
/**
* Block Renderer REST API.
*
* @package Canvas
*/
/**
* Controller which provides REST endpoint for rendering a block.
*
* @since 5.0.0
*
* @see WP_REST_Controller
*/
class CNVS_REST_Block_Renderer_Controller extends WP_REST_Controller {
/**
* Constructs the controller.
*
* @since 5.0.0
*/
public function __construct() {
$this->namespace = 'wp/v2';
$this->rest_base = 'canvas-renderer';
}
/**
* Registers the necessary REST API routes, one for each dynamic block.
*
* @since 5.0.0
*/
public function register_routes() {
$block_types = WP_Block_Type_Registry::get_instance()->get_all_registered();
foreach ( $block_types as $block_type ) {
if ( ! $block_type->is_dynamic() ) {
continue;
}
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/(?P<name>' . $block_type->name . ')',
array(
'args' => array(
'name' => array(
'description' => __( 'Unique registered name for the block.' ),
'type' => 'string',
),
),
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'args' => array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
'attributes' => array(
/* translators: %s is the name of the block */
'description' => sprintf( __( 'Attributes for %s block' ), $block_type->name ),
'type' => 'object',
'additionalProperties' => false,
'properties' => $block_type->get_attributes(),
'default' => array(),
),
'post_id' => array(
'description' => __( 'ID of the post context.' ),
'type' => 'integer',
),
),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
}
}
/**
* Checks if a given request has access to read blocks.
*
* @since 5.0.0
*
* @param WP_REST_Request $request Request.
* @return true|WP_Error True if the request has read access, WP_Error object otherwise.
*/
public function get_item_permissions_check( $request ) {
global $post;
$post_id = isset( $request['post_id'] ) ? intval( $request['post_id'] ) : 0;
if ( 0 < $post_id ) {
$post = get_post( $post_id );
if ( ! $post || ! current_user_can( 'edit_post', $post->ID ) ) {
return new WP_Error(
'block_cannot_read',
__( 'Sorry, you are not allowed to read blocks of this post.' ),
array(
'status' => rest_authorization_required_code(),
)
);
}
} else {
if ( ! current_user_can( 'edit_posts' ) ) {
return new WP_Error(
'block_cannot_read',
__( 'Sorry, you are not allowed to read blocks as this user.' ),
array(
'status' => rest_authorization_required_code(),
)
);
}
}
return true;
}
/**
* Returns block output from block's registered render_callback.
*
* @since 5.0.0
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function get_item( $request ) {
global $post;
$post_id = isset( $request['post_id'] ) ? intval( $request['post_id'] ) : 0;
if ( 0 < $post_id ) {
$post = get_post( $post_id );
// Set up postdata since this will be needed if post_id was set.
setup_postdata( $post );
}
$registry = WP_Block_Type_Registry::get_instance();
$block = $registry->get_registered( $request['name'] );
if ( null === $block ) {
return new WP_Error(
'block_invalid',
__( 'Invalid block.' ),
array(
'status' => 404,
)
);
}
$data = array(
'rendered' => $block->render( $request->get_param( 'attributes' ) ),
);
return rest_ensure_response( $data );
}
/**
* Retrieves block's output schema, conforming to JSON Schema.
*
* @since 5.0.0
*
* @return array Item schema data.
*/
public function get_item_schema() {
return array(
'$schema' => 'http://json-schema.org/schema#',
'title' => 'rendered-block',
'type' => 'object',
'properties' => array(
'rendered' => array(
'description' => __( 'The rendered block.' ),
'type' => 'string',
'required' => true,
'context' => array( 'edit' ),
),
),
);
}
}
add_action(
'rest_api_init', function() {
// Block Renderer.
$controller = new CNVS_REST_Block_Renderer_Controller();
$controller->register_routes();
}, 99
);
@@ -0,0 +1,186 @@
/**
* External dependencies
*/
import ReactSelect from 'react-select';
import { debounce } from 'throttle-debounce';
/**
* WordPress dependencies
*/
const {
Component,
} = wp.element;
const {
withSelect,
} = wp.data;
const {
BaseControl,
} = wp.components;
const cachedCategories = {};
function maybeCache( newCategories ) {
if ( ! newCategories || ! newCategories.length ) {
return;
}
if ( ! cachedCategories ) {
cachedCategories = {};
}
newCategories.forEach( ( postData ) => {
if ( ! cachedCategories[ postData.id ] ) {
cachedCategories[ postData.id ] = postData;
}
} );
}
/**
* Component
*/
class ComponentCategoriesSelectorControl extends Component {
constructor() {
super( ...arguments );
this.state = {
searchTerm: '',
};
this.updateSearchTerm = debounce( 300, this.updateSearchTerm.bind( this ) );
}
/**
* Update search term with debounce.
*
* @param {String} search - search term.
*/
updateSearchTerm( search ) {
this.setState( {
searchTerm: search,
} );
}
render() {
const {
value,
label,
help,
onChange,
allСategories,
findСategories,
} = this.props;
const {
searchTerm,
} = this.state;
const foundСategories = findСategories( searchTerm ) || [];
return (
<BaseControl
label={ label }
help={ help }
>
<ReactSelect
options={
foundСategories.map( ( catData ) => {
return {
value: catData.slug,
label: catData.name,
};
} )
}
value={ ( () => {
let result = value ? value.split(',') : [];
if ( result && result.length ) {
result = result.map( ( name ) => {
let thisData = {
value: name,
label: name,
};
// get label from categories list
for (var id in allСategories ) {
if ( name === allСategories[id].slug ) {
thisData.label = allСategories[id].name;
}
}
return thisData;
} );
return result;
}
return [];
} )() }
isMulti
name="filter-by-categories"
className="basic-multi-select"
classNamePrefix="select"
components={ {
IndicatorSeparator: () => null,
ClearIndicator: () => null,
} }
onInputChange={ ( val ) => {
this.updateSearchTerm( val );
} }
onChange={ ( val ) => {
let result = '';
let slug = '';
if ( val ) {
val.forEach( ( catData ) => {
result += slug + catData.value;
slug = ',';
} );
}
onChange( result );
} }
/>
</BaseControl>
);
}
}
export default withSelect( ( select, props, a, b, c ) => {
const {
getEntityRecords,
} = select( 'core' );
const {
value,
} = props;
let slugs = value ? value.split(',') : [];
// find non-cached categories and try to retrieve.
slugs = slugs.filter( ( slug ) => {
return ! cachedCategories || ! cachedCategories[ slug ];
} );
if ( slugs && slugs.length ) {
const newСategories = getEntityRecords( 'taxonomy', 'category', {
slug: slugs,
per_page: 100,
} );
maybeCache( newСategories );
}
return {
findСategories( search = '' ) {
const searchСategories = getEntityRecords( 'taxonomy', 'category', {
search,
per_page: 20,
} );
maybeCache( searchСategories );
return searchСategories;
},
allСategories: cachedCategories || {},
};
})( ComponentCategoriesSelectorControl );
@@ -0,0 +1,56 @@
/**
* WordPress dependencies
*/
const {
Component,
} = wp.element;
const {
ColorPalette,
} = wp.blockEditor;
/**
* Component
*/
export default class ComponentColors extends Component {
constructor() {
super(...arguments);
this.updateColors = this.updateColors.bind(this);
}
/**
* Update colors value.
*
* @param {String} slug - slug.
* @param {String} prefix - type prefix.
* @param {String} suffix - responsive suffix.
* @param {String} val - new value.
*/
updateColors(slug, suffix = '', val) {
const {
onChange,
} = this.props;
const updateAttrs = {
[slug + suffix]: val,
};
onChange(updateAttrs);
}
render() {
const {
slug = '',
val = '',
suffix = '',
} = this.props;
return (
<ColorPalette
value={ val || '' }
onChange={ (val) => this.updateColors(slug, suffix, val) }
/>
);
}
}
@@ -0,0 +1,86 @@
/**
* WordPress dependencies
*/
const {
__,
} = wp.i18n;
const {
Component,
Fragment,
} = wp.element;
const {
TextControl,
Notice,
} = wp.components;
/**
* Component
*/
export default class ComponentDimensionControl extends Component {
constructor() {
super( ...arguments );
this.isValidValue = this.isValidValue.bind( this );
}
/**
* Check if value valid.
*
* @param {Mixed} value value to check.
* @returns {Boolean}
*/
isValidValue( value ) {
const validUnits = [ 'fr', 'rem', 'em', 'ex', '%', 'px', 'cm', 'mm', 'in', 'pt', 'pc', 'ch', 'vh', 'vw', 'vmin', 'vmax' ];
// Whitelist values.
if ( ! value || '' === value || 0 === value || '0' === value || 'auto' === value || 'inherit' === value || 'initial' === value ) {
return true;
}
// Skip checking if calc().
if ( 0 <= value.indexOf( 'calc(' ) && 0 <= value.indexOf( ')' ) ) {
return true;
}
// Get the numeric value.
const numericValue = parseFloat( value );
// Get the unit
const unit = value.replace( numericValue, '' );
// Allow unitless.
if ( ! unit ) {
return true;
}
// Check the validity of the numeric value and units.
return ( ! isNaN( numericValue ) && -1 !== validUnits.indexOf( unit ) );
}
render() {
const {
value,
label,
help,
onChange,
} = this.props;
return (
<Fragment>
<TextControl
label={ label }
help={ help }
value={ value }
onChange={ onChange }
/>
{ ! this.isValidValue( value ) ? (
<Notice status="warning" isDismissible={ false }>
{ __( 'Invalid dimension value.' ) }
</Notice>
) : '' }
</Fragment>
);
}
}
@@ -0,0 +1,192 @@
/**
* Internal dependencies
*/
import isFieldVisible from '../../utils/is-field-visible';
/**
* WordPress dependencies
*/
const {
Component,
} = wp.element;
const {
canvasBreakpoints,
canvasAllBreakpoints,
} = window;
/**
* Component
*/
export default class ComponentFieldsCSSOutput extends Component {
constructor() {
super( ...arguments );
this.prepareStylesFromParams = this.prepareStylesFromParams.bind( this );
}
/**
* Get current field value. If value doesn't exist, use default value.
*
* @param {Object} fieldData field data.
* @param {String} breakpoint breakpoint name.
*
* @returns {Mixed} field value.
*/
getFieldValue( fieldData, breakpoint = '' ) {
const {
attributes = {},
} = this.props;
let suffix = '';
if ( breakpoint ) {
suffix = `_${ breakpoint }`;
}
if ( typeof attributes[ fieldData.key + suffix ] !== 'undefined' ) {
return attributes[ fieldData.key + suffix ];
} else if ( typeof fieldData[ 'default' + suffix ] !== 'undefined' ) {
return fieldData[ 'default' + suffix ];
}
return null;
}
/**
* Prepare styles from params
* Params example:
{
'element' : '$',
'property' : 'height',
'value_pattern' : 'linear-gradient(to bottom, $ 14%,#7db9e8 77%)',
'media_query' : '@media ( min-width: 760px )',
'units' : 'px',
'prefix' : 'calc(1px + ',
'suffix' : ') !important',
}
*
* @param {String} selector CSS selector.
* @param {Mixed} value Property value.
* @param {Object} params Output params.
*
* @returns {String}
*/
prepareStylesFromParams( selector, value, params ) {
let result = '';
if ( ! selector || typeof value === 'undefined' || '' === value || null === value || ! params.property ) {
return result;
}
// check for context
if ( params.context && params.context.indexOf( 'editor' ) === -1 ) {
return result;
}
// Reverse smart selector.
if ( params.reverse && params.reverse_max ) {
let multi_selector = '';
for (let iteration = 1; iteration < params.reverse_max; iteration++) {
if ( iteration <= value ) {
continue;
}
multi_selector += ( multi_selector ? ', ' : '' ) + params.reverse.replace( /\$numb/g, iteration );
}
selector = multi_selector;
}
// value pattern
if ( params.value_pattern ) {
value = params.value_pattern.replace( /\$/g, value );
}
// prepare CSS
result = `
${ params.element ? params.element.replace( /\$/g, selector ) : selector } {
${ params.property }: ${ params.prefix || '' }${ value }${ params.units || '' }${ params.suffix || '' };
}
`;
// add media query
if ( params.media_query ) {
result = `
${ params.media_query } {
${ result }
}
`;
}
return result;
}
render() {
const {
selector = '',
fields = [],
attributes,
} = this.props;
let result = '';
fields
.filter( ( fieldData ) => {
if ( ! fieldData || ! fieldData.type ) {
return false;
}
// check active_callback
return isFieldVisible( fieldData, attributes, fields );
})
.forEach( ( fieldData, i ) => {
if ( selector && fieldData.output ) {
fieldData.output.forEach( ( outputData ) => {
// general styles.
result += this.prepareStylesFromParams( selector, this.getFieldValue( fieldData ), outputData );
// Dark styles.
if ( canvasSchemes && ( 'color' === fieldData.type ) ) {
Object.keys( canvasSchemes ).forEach( ( name ) => {
if ( name && 'default' !== name ) {
let rule = `[data-scheme="${name}"] ${selector}`;
result += this.prepareStylesFromParams( rule, this.getFieldValue( fieldData, name ), {
...outputData
} );
}
} );
}
// Responsive styles.
if ( fieldData.responsive ) {
Object.keys( canvasAllBreakpoints ).forEach( ( name ) => {
if ( name && 'desktop' !== name ) {
let rule = selector;
// If exist scheme.
if ( canvasAllBreakpoints[ name ].scheme ) {
rule = `[data-scheme="${name}"] ${canvasAllBreakpoints[ name ].scheme}`;
}
result += this.prepareStylesFromParams( rule, this.getFieldValue( fieldData, name ), {
...outputData,
media_query: `@media (max-width: ${ canvasAllBreakpoints[ name ].width }px)`,
} );
}
} );
}
} );
}
} );
return (
result ? (
<style>
{ result }
</style>
) : ''
);
}
}
@@ -0,0 +1,200 @@
<?php
/**
* Parse fields data and generate styles output.
*
* @package Canvas
*/
require_once CNVS_PATH . 'gutenberg/utils/is-field-visible/index.php';
if ( ! class_exists( 'CNVS_Gutenberg_Fields_CSS_Output' ) ) {
/**
* Class Gutenberg Fields CSS Output.
*/
class CNVS_Gutenberg_Fields_CSS_Output {
/**
* Prepare CSS for selected fields.
*
* @param string $selector CSS selector.
* @param array $fields Fields data.
* @param array $attributes Block attributes.
*
* @return string
*/
public static function get( $selector, $fields = array(), $attributes = array() ) {
$schemes = cnvs_gutenberg()->get_schemes_data();
$breakpoints = cnvs_gutenberg()->get_breakpoints_data();
$all_breakpoints = cnvs_gutenberg()->get_all_breakpoints_data();
$result = '';
foreach ( $fields as $field ) {
if ( ! isset( $field['type'] ) || ! isset( $field['output'] ) ) {
continue;
}
// check if field visible.
if ( ! CNVS_Gutenberg_Utils_Is_Field_Visible::check( $field, $attributes, $fields ) ) {
continue;
}
foreach ( $field['output'] as $data ) {
$result .= self::prepare_styles_from_params( $selector, self::get_field_value( $field, $attributes ), $data );
if ( $schemes && ( 'color' === $field['type'] ) ) {
foreach ( $schemes as $name => $scheme ) {
$rule = sprintf( '[data-scheme="%s"] %s', $name, $selector );
if ( $name && 'default' !== $name ) {
$result .= self::prepare_styles_from_params(
$rule,
self::get_field_value( $field, $attributes, $name ),
$data
);
}
}
}
if ( isset( $field['responsive'] ) && $field['responsive'] ) {
foreach ( $all_breakpoints as $name => $breakpoint ) {
if ( $name && 'desktop' !== $name ) {
$rule = $selector;
// If exist scheme.
if ( isset( $breakpoint['scheme'] ) ) {
$rule = sprintf( '[data-scheme="%s"] %s', $breakpoint['scheme'], $selector );
}
$result .= self::prepare_styles_from_params(
$rule,
self::get_field_value( $field, $attributes, $name ),
array_merge(
$data,
array(
'media_query' => '@media (max-width: ' . apply_filters( 'canvas_blocks_dynamic_breakpoint_width', $breakpoint['width'], $field ) . 'px)',
)
)
);
}
}
}
}
}
return $result;
}
/**
* Get current field value. If value doesn't exist, use default value.
*
* @param array $field Field data.
* @param array $attributes Field attributes.
* @param string $breakpoint Breakpoint name.
*
* @return mixed Field value.
*/
public static function get_field_value( $field, $attributes, $breakpoint = '' ) {
$suffix = '';
if ( $breakpoint ) {
$suffix = '_' . $breakpoint;
}
if ( isset( $attributes[ $field['key'] . $suffix ] ) ) {
return $attributes[ $field['key'] . $suffix ];
} elseif ( isset( $field[ 'default' . $suffix ] ) ) {
return $field[ 'default' . $suffix ];
}
return null;
}
/**
* Prepare styles from params
* Params example:
array(
'element' => '$',
'property' => 'height',
'value_pattern' => 'linear-gradient(to bottom, $ 14%,#7db9e8 77%)',
'media_query' => '@media ( min-width: 760px )',
'units' => 'px',
'prefix' => 'calc(1px + ',
'suffix' => ') !important',
)
*
* @param string $selector CSS selector.
* @param mixed $value Property value.
* @param array $params Output params.
*
* @return string
*/
public static function prepare_styles_from_params( $selector, $value, $params ) {
$result = '';
if ( ! $selector || ! isset( $value ) || '' === $value || null === $value || ! isset( $params['property'] ) ) {
return $result;
}
// Check for context.
if ( isset( $params['context'] ) && ! in_array( 'front', $params['context'], true ) ) {
return $result;
}
// Custom selector pattern.
if ( isset( $params['element'] ) ) {
$selector = str_replace( '$', $selector, $params['element'] );
}
// Reverse smart selector.
if ( isset( $params['reverse'] ) && isset( $params['reverse_max'] ) ) {
$multi_selector = __return_empty_string();
for ( $iteration = 1; $iteration <= $params['reverse_max']; $iteration++ ) {
if ( $iteration <= $value ) {
continue;
}
$multi_selector .= ( $multi_selector ? ', ' : '' ) . str_replace( '$numb', $iteration, $params['reverse'] );
}
$selector = str_replace( '$', $selector, $multi_selector );
}
// Value pattern.
if ( isset( $params['value_pattern'] ) ) {
$value = str_replace( '$', $value, $params['value_pattern'] );
}
// Prefix.
if ( isset( $params['prefix'] ) ) {
$value = $params['prefix'] . $value;
}
// Units.
if ( isset( $params['units'] ) ) {
$value = $value . $params['units'];
}
// Suffix.
if ( isset( $params['suffix'] ) ) {
$value = $value . $params['suffix'];
}
$property = $params['property'];
// Prepare CSS.
$result = "${selector} { ${property}: ${value}; }";
// Add media query.
if ( isset( $params['media_query'] ) ) {
$media_query = $params['media_query'];
$result = "${media_query} { ${result} }";
}
return $result;
}
}
}
@@ -0,0 +1,857 @@
/**
* External dependencies
*/
import ReactSelect from 'react-select';
/**
* Internal dependencies
*/
import './style.scss';
import SchemeWrapper from '../scheme-wrapper';
import ResponsiveWrapper from '../responsive-wrapper';
import DimensionControl from '../dimension-control';
import CategoriesSelectorControl from '../categories-selector-control';
import TagsSelectorControl from '../tags-selector-control';
import PostsSelectorControl from '../posts-selector-control';
import QueryControl from '../query-control';
import isFieldVisible from '../../utils/is-field-visible';
/**
* WordPress dependencies
*/
const {
__,
sprintf,
} = wp.i18n;
const {
Component,
Fragment,
RawHTML,
} = wp.element;
const {
BaseControl,
ToggleControl,
TextControl,
TextareaControl,
RangeControl,
SelectControl,
PanelBody,
Notice,
DropZone,
Button,
Toolbar,
} = wp.components;
const {
ColorPalette,
MediaPlaceholder,
MediaUpload,
mediaUpload,
} = wp.blockEditor;
const {
applyFilters,
} = wp.hooks;
/**
* Component
*/
export default class ComponentFieldsRender extends Component {
constructor() {
super( ...arguments );
this.getAllFieldsSections = this.getAllFieldsSections.bind( this );
this.getFieldValue = this.getFieldValue.bind( this );
this.updateFieldValue = this.updateFieldValue.bind( this );
this.renderControl = this.renderControl.bind( this );
}
/**
* Get all available sections.
*
* @returns {Object} sections.
*/
getAllFieldsSections() {
const {
fields = [],
} = this.props;
const sections = {
...{ '': '' },
...this.props.sections,
};
// check all fields and add section if not defined.
fields.forEach( ( field ) => {
if ( field.section && typeof sections[ field.section ] === 'undefined' ) {
sections[ field.section ] = field.section;
}
} );
return sections;
}
/**
* Get current field value. If value doesn't exist, use default value.
*
* @param {Object} fieldData field data.
* @param {String} suffix attribute name suffix.
*
* @returns {Mixed} field value.
*/
getFieldValue( fieldData, suffix = '' ) {
const {
attributes = {},
} = this.props;
if ( typeof attributes[ fieldData.key + suffix ] !== 'undefined' ) {
return attributes[ fieldData.key + suffix ];
} else if ( typeof fieldData[ 'default' + suffix ] !== 'undefined' ) {
return fieldData[ 'default' + suffix ];
}
return null;
}
/**
* Update current field value.
*
* @param {Object} fieldData field data.
* @param {Mixed} val field value.
* @param {String} suffix attribute name suffix.
*/
updateFieldValue( fieldData, val, suffix = '' ) {
const {
onChange,
} = this.props;
onChange( fieldData.key + suffix, val );
}
/**
* Render control
*
* @param {Object} fieldData field data.
*
* @returns {JSX}
*/
renderControl( fieldData ) {
const renderName = `renderControl${ fieldData.type.replace( /(\b\w)|(-.)/g, ( x ) => ( x[1] || x[0] ).toUpperCase() ) }`;
// check if render method exist.
if (this[renderName]) {
return (
<SchemeWrapper>
{({ schemeSuffix, ComponentSchemeDropdown }) => {
return (
<ResponsiveWrapper>
{({ responsiveSuffix, ComponentResponsiveDropdown }) => {
let fieldSuffix = '';
let newFieldData = { ...fieldData };
// Scheme dropdown.
if (canvasSchemes && ('color' === newFieldData.type)) {
fieldSuffix += schemeSuffix;
newFieldData = {
...newFieldData,
label: (
<Fragment>
{newFieldData.label || ''}
<ComponentSchemeDropdown />
</Fragment>
)
};
}
// Responsive dropdown.
if (newFieldData.responsive) {
fieldSuffix += responsiveSuffix;
newFieldData = {
...newFieldData,
label: (
<Fragment>
{newFieldData.label || ''}
<ComponentResponsiveDropdown />
</Fragment>
)
};
}
return this[renderName](
newFieldData,
this.getFieldValue(newFieldData, fieldSuffix),
(val) => {
this.updateFieldValue(newFieldData, val, fieldSuffix);
}
);
}}
</ResponsiveWrapper>
)
}}
</SchemeWrapper>
);
}
// render method does not exist.
return (
<Notice status="warning" isDismissible={ false }>
{ sprintf( __( 'Unfortunately, `%s` method doesn\'t exist.' ), renderName ) }
</Notice>
);
}
/**
* Render Separator control
*
* @param {Object} fieldData field data.
* @param {String} val field value.
* @param {Object} onChange field on change callback.
*
* @returns {JSX}
*/
renderControlSeparator( fieldData, val, onChange ) {
return (
<hr />
);
}
/**
* Render Text control
*
* @param {Object} fieldData field data.
* @param {String} val field value.
* @param {Object} onChange field on change callback.
*
* @returns {JSX}
*/
renderControlText( fieldData, val, onChange ) {
return (
<TextControl
label={ fieldData.label || false }
help={ <RawHTML>{ fieldData.help || '' }</RawHTML> }
value={ val }
onChange={ onChange }
/>
);
}
/**
* Render Textarea control
*
* @param {Object} fieldData field data.
* @param {String} val field value.
* @param {Object} onChange field on change callback.
*
* @returns {JSX}
*/
renderControlTextarea( fieldData, val, onChange ) {
return (
<TextareaControl
label={ fieldData.label || false }
help={ fieldData.help || false }
value={ val }
onChange={ onChange }
/>
);
}
/**
* Render Toggle control
*
* @param {Object} fieldData field data.
* @param {Boolean} val field value.
* @param {Object} onChange field on change callback.
*
* @returns {JSX}
*/
renderControlToggle( fieldData, val, onChange ) {
return (
<ToggleControl
label={ fieldData.label || false }
help={ fieldData.help || false }
checked={ !! val }
onChange={ onChange }
/>
);
}
/**
* Render Toggle List control
*
* @param {Object} fieldData field data.
* @param {Object} val field value.
* @param {Object} onChange field on change callback.
*
* @returns {JSX}
*/
renderControlToggleList( fieldData, val, onChange ) {
return (
<BaseControl
label={ fieldData.label || false }
help={ fieldData.help || false }
>
<p />
{ Object.keys( val ).map( ( valName ) => {
return (
<ToggleControl
key={ `toggle-list-control-${ fieldData.key }-${ valName }` }
label={ fieldData.choices[ valName ] || false }
checked={ !! val[ valName ] }
onChange={ () => {
const result = Object.assign( {}, val );
result[ valName ] = ! result[ valName ];
onChange( result );
} }
/>
);
} ) }
</BaseControl>
);
}
/**
* Render Dimension control
*
* @param {Object} fieldData field data.
* @param {String} val field value.
* @param {Object} onChange field on change callback.
*
* @returns {JSX}
*/
renderControlDimension( fieldData, val, onChange ) {
return (
<DimensionControl
label={ fieldData.label || false }
help={ fieldData.help || false }
value={ val }
onChange={ onChange }
/>
);
}
/**
* Render Number control
*
* @param {Object} fieldData field data.
* @param {Number} val field value.
* @param {Object} onChange field on change callback.
*
* @returns {JSX}
*/
renderControlNumber( fieldData, val, onChange ) {
return (
<RangeControl
label={ fieldData.label || false }
help={ fieldData.help || false }
min={ fieldData.min || false }
max={ fieldData.max || false }
step={ fieldData.step || 1 }
value={ val }
onChange={ onChange }
/>
);
}
/**
* Render Icon Buttons control
*
* @param {Object} fieldData field data.
* @param {String} val field value.
* @param {Object} onChange field on change callback.
*
* @returns {JSX}
*/
renderControlIconButtons( fieldData, val, onChange ) {
return (
<BaseControl
label={ fieldData.label || false }
help={ fieldData.help || false }
>
<Toolbar
className="cnvs-control-icon-buttons"
controls={ Object.keys( fieldData.choices ).map( ( option ) => {
return {
icon: <RawHTML className="cnvs-control-icon-buttons-svg">{ fieldData.choices[ option ] }</RawHTML>,
isActive: val === option,
onClick() {
onChange( val === option ? '' : option );
},
};
} ) }
/>
</BaseControl>
);
}
/**
* Render Select control
*
* @param {Object} fieldData field data.
* @param {String} val field value.
* @param {Object} onChange field on change callback.
*
* @returns {JSX}
*/
renderControlSelect( fieldData, val, onChange ) {
const options = Object.keys( fieldData.choices ).map( ( option ) => {
return {
label: fieldData.choices[ option ],
value: option,
};
} );
return (
<SelectControl
label={ fieldData.label || false }
help={ fieldData.help || false }
multiple={ fieldData.multiple || false }
value={ val }
options={ options }
onChange={ ( val ) => {
onChange( val );
} }
/>
);
}
/**
* Render React Select control
*
* @param {Object} fieldData field data.
* @param {String} val field value.
* @param {Object} onChange field on change callback.
*
* @returns {JSX}
*/
renderControlReactSelect( fieldData, val, onChange ) {
const options = Object.keys( fieldData.choices ).map( ( option ) => {
return {
label: fieldData.choices[ option ],
value: option,
};
} );
return (
<BaseControl
label={ fieldData.label || false }
help={ fieldData.help || false }
>
<ReactSelect
isMulti={ fieldData.multiple || false }
name="colors"
options={ options }
value={ ( () => {
if ( fieldData.multiple ) {
if ( ! Array.isArray( val ) ) {
val = [];
}
// options
const result = val.map( ( val ) => {
return {
value: val,
label: fieldData.choices[ val ] || val,
};
} );
return result;
}
return val;
} )() }
onChange={ ( val ) => {
if ( fieldData.multiple ) {
if ( val ) {
const result = val.map( ( opt ) => {
return opt.value;
} );
onChange( result );
} else {
onChange( [] );
}
} else {
onChange( val );
}
} }
/>
</BaseControl>
);
}
/**
* Render Color Picker control
*
* @param {Object} fieldData field data.
* @param {String} val field value.
* @param {Object} onChange field on change callback.
*
* @returns {JSX}
*/
renderControlColor( fieldData, val, onChange ) {
let result = (
<ColorPalette
value={ val || '' }
onChange={ onChange }
/>
);
if ( fieldData.label || fieldsData.help ) {
return (
<BaseControl
label={ fieldData.label || false }
help={ fieldData.help || false }
>
{ result }
</BaseControl>
);
}
return result;
}
/**
* Render Image control
*
* @param {Object} fieldData field data.
* @param {Number} val field value.
* @param {Object} onChange field on change callback.
*
* @returns {JSX}
*/
renderControlImage( fieldData, val = {}, onChange ) {
const {
id = 0,
url = '',
} = val;
return (
<Fragment>
<BaseControl
label={ fieldData.label || false }
help={ fieldData.help || false }
>
{ ! id ? (
<MediaPlaceholder
icon="format-image"
labels={ {
title: __( 'Image' ),
name: __( 'image' ),
} }
onSelect={ ( image ) => {
onChange( {
id: image.id,
url: image.url,
} );
} }
accept="image/*"
allowedTypes={ [ 'image' ] }
disableMaxUploadErrorMessages
onError={ ( e ) => {
console.log( e );
} }
/>
) : '' }
{ url ? (
<Fragment>
<DropZone
onFilesDrop={ ( files ) => {
mediaUpload( {
allowedTypes: [ 'image' ],
filesList: files,
onFileChange: ( image ) => {
onChange( {
id: image.id,
url: image.url,
} );
},
onError( e ) {
console.log( e );
},
} );
} }
/>
{ url ? (
<img src={ url } />
) : '' }
<div>
<Button
isDefault={ true }
onClick={ () => {
onChange( {
id: 0,
url: '',
} );
} }
>
{ __( 'Remove Image' ) }
</Button>
</div>
</Fragment>
) : '' }
</BaseControl>
</Fragment>
);
}
/**
* Render Gallery control
*
* @param {Object} fieldData field data.
* @param {Number} val field value.
* @param {Object} onChange field on change callback.
*
* @returns {JSX}
*/
renderControlGallery( fieldData, val = [], onChange ) {
const ALLOWED_MEDIA_TYPES = [ 'image' ];
return (
<BaseControl
label={ fieldData.label || false }
help={ fieldData.help || false }
>
{ ! val || ! val.length ? (
<MediaPlaceholder
icon="format-gallery"
labels={ {
title: fieldData.label,
name: __( 'images' ),
} }
onSelect={ ( images ) => {
const result = images.map( ( image ) => {
return image.id;
} );
onChange( result );
} }
accept="image/*"
allowedTypes={ ALLOWED_MEDIA_TYPES }
disableMaxUploadErrorMessages
multiple
onError={ ( e ) => {
// eslint-disable-next-line no-console
console.log( e );
} }
/>
) : '' }
{ val && val.length ? (
<MediaUpload
onSelect={ ( images ) => {
const result = images.map( ( image ) => {
return image.id;
} );
onChange( result );
} }
allowedTypes={ ALLOWED_MEDIA_TYPES }
multiple
gallery
value={ val }
render={ ( { open } ) => (
<div
className="cnvs-gutenberg-component-gallery"
onClick={ open }
role="presentation"
>
<DropZone
onFilesDrop={ ( files ) => {
const currentImages = val || [];
mediaUpload( {
allowedTypes: ALLOWED_MEDIA_TYPES,
filesList: files,
onFileChange: ( images ) => {
const result = images.map( ( image ) => {
return image.id;
} );
onChange( currentImages.concat( result ) );
},
onError( e ) {
// eslint-disable-next-line no-console
console.log( e );
},
} );
} }
/>
{ val ? (
<div className="cnvs-gutenberg-component-gallery-list">
{val.map(imageId => {
return (
<img src={ canvasLocalize.ajaxURL + '?action=cnvs_render_thumbnail&image_id=' + imageId } />
)
})}
</div>
) : '' }
<div className="cnvs-gutenberg-component-gallery-button">
<Button isDefault={ true }>{ __( 'Edit Gallery' ) }</Button>
</div>
</div>
) }
/>
) : '' }
</BaseControl>
);
}
/**
* Render Categories Selector control
*
* @param {Object} fieldData field data.
* @param {String} val field value.
* @param {Object} onChange field on change callback.
*
* @returns {JSX}
*/
renderControlCategoriesSelector( fieldData, val, onChange ) {
return (
<CategoriesSelectorControl
label={ fieldData.label || false }
help={ fieldData.help || false }
value={ val }
onChange={ onChange }
/>
);
}
/**
* Render Tags Selector control
*
* @param {Object} fieldData field data.
* @param {String} val field value.
* @param {Object} onChange field on change callback.
*
* @returns {JSX}
*/
renderControlTagsSelector( fieldData, val, onChange ) {
return (
<TagsSelectorControl
label={ fieldData.label || false }
help={ fieldData.help || false }
value={ val }
onChange={ onChange }
/>
);
}
/**
* Render Posts Selector control
*
* @param {Object} fieldData field data.
* @param {String} val field value.
* @param {Object} onChange field on change callback.
*
* @returns {JSX}
*/
renderControlPostsSelector( fieldData, val, onChange ) {
return (
<PostsSelectorControl
label={ fieldData.label || false }
help={ fieldData.help || false }
value={ val }
onChange={ onChange }
/>
);
}
/**
* Render Query control
*
* @param {Object} fieldData field data.
* @param {String} val field value.
* @param {Object} onChange field on change callback.
*
* @returns {JSX}
*/
renderControlQuery( fieldData, val, onChange ) {
return (
<QueryControl
label={ fieldData.label || false }
help={ fieldData.help || false }
value={ val }
onChange={ onChange }
/>
);
}
render() {
const {
fields = [],
attributes,
} = this.props;
const sections = this.getAllFieldsSections();
return (
<Fragment>
{ Object.keys( sections ).map( ( sectionName ) => {
const sectionTitle = sections[ sectionName ].title;
const initialOpen = sections[ sectionName ].open || ( sectionTitle ? false : true );
if ( ! fields || ! fields.length ) {
return '';
}
const sectionFields = fields
.filter( ( fieldData ) => {
if ( ! fieldData || ! fieldData.type ) {
return false;
}
// prevent invisible fields, that used only for registering block attributes.
if ( 'type-string' === fieldData.type || 'type-number' === fieldData.type || 'type-boolean' === fieldData.type || 'type-array' === fieldData.type ) {
return false;
}
// limit fields for current section only.
if ( sectionName && fieldData.section !== sectionName ) {
return false;
} else if ( ! sectionName && fieldData.section ) {
return false;
}
// check active_callback
return isFieldVisible( fieldData, attributes, fields );
})
.map( ( fieldData, i ) => {
let fieldKey = `field-${ fieldData.type }-${ i }`;
return (
applyFilters( 'canvas.component.fieldsRender.singleField', (
<Fragment key={ fieldKey }>
{ this.renderControl( fieldData ) }
</Fragment>
), {
fieldData,
props: this.props,
} )
);
} );
if ( ! sectionFields || ! sectionFields.length ) {
return '';
}
return (
<PanelBody
key={ `section-${ sectionName }` }
title={ sectionTitle }
initialOpen={ initialOpen }
>
{ applyFilters( 'canvas.component.fieldsRender', sectionFields, {
sectionName,
sectionTitle,
props: this.props,
} ) }
</PanelBody>
);
} ) }
</Fragment>
);
}
}
@@ -0,0 +1,101 @@
/**
* External dependencies
*/
import classnames from 'classnames';
/**
* Internal dependencies
*/
import './style.scss';
/**
* WordPress dependencies
*/
const {
Component,
} = wp.element;
const {
Button,
Popover,
} = wp.components;
/**
* Component
*/
export default class ComponentImageSelector extends Component {
constructor() {
super( ...arguments );
this.state = {
showPopover: false,
};
}
render() {
let {
className,
value,
} = this.props;
const {
items = [],
onChange,
} = this.props;
className = classnames(
'cnvs-component-image-selector',
className
);
return (
<div className={ className }>
{ items && items.length ? (
items.map( ( itemData, i ) => {
const isDisabled = itemData.isDisabled;
const disabledNotice = itemData.disabledNotice;
const itemClassName = classnames(
'cnvs-component-image-selector-item',
{
'cnvs-component-image-selector-item-active': value === itemData.value,
'cnvs-component-image-selector-item-disabled': isDisabled,
}
);
return (
<div
key={ `cnvs-component-image-selector-item-${ itemData.value }` }
className={ itemClassName }
>
<Button
onClick={ () => {
if ( ! isDisabled ) {
onChange( itemData.value );
} else {
this.setState( { showPopover: itemData.value } );
}
} }
>
{ itemData.content }
{ this.state.showPopover === itemData.value && disabledNotice ? (
<Popover
className="cnvs-component-image-selector-item-popover"
focusOnMount={ false }
onClickOutside={ () => {
this.setState( { showPopover: false } );
} }
>
{ disabledNotice }
</Popover>
) : '' }
</Button>
<span>{ itemData.label }</span>
</div>
);
} )
) : '' }
</div>
);
}
}
@@ -0,0 +1,114 @@
/**
* External dependencies
*/
import ReactSelect from 'react-select';
/**
* WordPress dependencies
*/
const {
Component,
} = wp.element;
const {
withSelect,
} = wp.data;
const {
BaseControl,
} = wp.components;
/**
* Component
*/
class ComponentPostFormatsSelectorControl extends Component {
constructor() {
super( ...arguments );
}
render() {
const {
value,
label,
help,
onChange,
postFormats,
} = this.props;
if ( ! postFormats.length ) {
return '';
}
return (
<BaseControl
label={ label }
help={ help }
>
<ReactSelect
options={ postFormats }
value={ ( () => {
let result = value ? value.split(',') : [];
if ( result && result.length ) {
result = result.map( ( name ) => {
let thisData = {
value: name,
label: name,
};
// get label from formats list
postFormats.map( ( formatData ) => {
if ( formatData.name === name ) {
thisData.label = formatData.name;
}
} )
return thisData;
} );
return result;
}
return [];
} )() }
isMulti
name="filter-by-formats"
className="basic-multi-select"
classNamePrefix="select"
components={ {
IndicatorSeparator: () => null,
ClearIndicator: () => null,
} }
onChange={ ( val ) => {
let result = '';
let slug = '';
if ( val ) {
val.forEach( ( formatData ) => {
result += slug + formatData.value;
slug = ',';
} );
}
onChange( result );
} }
/>
</BaseControl>
);
}
}
export default withSelect( ( select, props ) => {
const {
getThemeSupports,
} = select( 'core' );
const themeSupports = getThemeSupports();
const postFormats = themeSupports.formats ? themeSupports.formats : [];
return {
postFormats: postFormats.map( ( format ) => {
return {
label: format,
value: format,
};
} ),
};
})( ComponentPostFormatsSelectorControl );
@@ -0,0 +1,81 @@
/**
* WordPress dependencies
*/
const {
Component,
} = wp.element;
const {
withSelect,
} = wp.data;
const {
SelectControl,
} = wp.components;
const {
applyFilters,
} = wp.hooks;
/**
* Component
*/
class ComponentPostTypeSelectorControl extends Component {
constructor() {
super( ...arguments );
}
render() {
const {
value,
label,
help,
onChange,
postTypes,
} = this.props;
return (
<SelectControl
label={ label }
help={ help }
value={ value }
options={ applyFilters( 'canvas.selector.postTypes', postTypes ) }
onChange={ ( val ) => {
onChange( val );
} }
/>
);
}
}
export default withSelect( ( select, props ) => {
const {
getPostTypes,
} = select( 'core' );
const {
value,
} = props;
const postTypes = getPostTypes();
return {
postTypes: postTypes ? (
postTypes
.filter( ( postType ) => {
return postType.viewable;
} )
.map( ( postType ) => {
return {
label: postType.name,
value: postType.slug,
};
} )
) : [
{
label: value,
value: value,
},
],
};
})( ComponentPostTypeSelectorControl );
@@ -0,0 +1,184 @@
/**
* External dependencies
*/
import ReactSelect from 'react-select';
import { debounce } from 'throttle-debounce';
/**
* WordPress dependencies
*/
const {
Component,
} = wp.element;
const {
withSelect,
} = wp.data;
const {
BaseControl,
} = wp.components;
const cachedPosts = {};
function maybeCache( postType, newPosts ) {
if ( ! newPosts || ! newPosts.length ) {
return;
}
if ( ! cachedPosts[ postType ] ) {
cachedPosts[ postType ] = {};
}
newPosts.forEach( ( postData ) => {
if ( ! cachedPosts[ postType ][ postData.id ] ) {
cachedPosts[ postType ][ postData.id ] = postData;
}
} );
}
/**
* Component
*/
class ComponentPostsSelectorControl extends Component {
constructor() {
super( ...arguments );
this.state = {
searchTerm: '',
};
this.updateSearchTerm = debounce( 300, this.updateSearchTerm.bind( this ) );
}
/**
* Update search term with debounce.
*
* @param {String} search - search term.
*/
updateSearchTerm( search ) {
this.setState( {
searchTerm: search,
} );
}
render() {
const {
value,
label,
help,
onChange,
allPosts,
findPosts,
} = this.props;
const {
searchTerm,
} = this.state;
const foundPosts = findPosts( searchTerm ) || [];
return (
<BaseControl
label={ label }
help={ help }
>
<ReactSelect
options={
foundPosts.map( ( postData ) => {
return {
value: postData.id,
label: postData.title.raw,
};
} )
}
value={ ( () => {
let result = value ? value.split(',') : [];
if ( result && result.length ) {
result = result.map( ( id ) => {
let thisData = {
value: id,
label: id,
};
// get label from categories list
if ( allPosts[ id ] ) {
thisData.label = allPosts[ id ].title.raw;
}
return thisData;
} );
return result;
}
return [];
} )() }
isMulti
name="filter-by-categories"
className="basic-multi-select"
classNamePrefix="select"
components={ {
IndicatorSeparator: () => null,
ClearIndicator: () => null,
} }
onInputChange={ ( val ) => {
this.updateSearchTerm( val );
} }
onChange={ ( val ) => {
let result = '';
let slug = '';
if ( val ) {
val.forEach( ( catData ) => {
result += slug + catData.value;
slug = ',';
} );
}
onChange( result );
} }
/>
</BaseControl>
);
}
}
export default withSelect( ( select, props, a, b, c ) => {
const {
getEntityRecords,
} = select( 'core' );
const {
value,
postType = 'post',
} = props;
let ids = value ? value.split(',') : [];
// find non-cached posts and try to retrieve.
ids = ids.filter( ( id ) => {
return ! cachedPosts[ postType ] || ! cachedPosts[ postType ][ id ];
} );
if ( ids && ids.length ) {
const newPosts = getEntityRecords( 'postType', postType, {
include: ids,
per_page: 100,
} );
maybeCache( postType, newPosts );
}
return {
findPosts( search = '' ) {
const searchPosts = getEntityRecords( 'postType', postType, {
search,
per_page: 20,
} );
maybeCache( postType, searchPosts );
return searchPosts;
},
allPosts: cachedPosts[ postType ] || {},
};
})( ComponentPostsSelectorControl );
@@ -0,0 +1,255 @@
/**
* Internal dependencies
*/
import PostTypeSelectorControl from '../post-type-selector-control';
import PostFormatsSelectorControl from '../post-formats-selector-control';
import CategoriesSelectorControl from '../categories-selector-control';
import TagsSelectorControl from '../tags-selector-control';
import PostsSelectorControl from '../posts-selector-control';
/**
* WordPress dependencies
*/
const {
__,
} = wp.i18n;
const {
Component,
Fragment,
} = wp.element;
const {
TextControl,
SelectControl,
} = wp.components;
/**
* Component
*/
export default class ComponentQueryControl extends Component {
constructor() {
super( ...arguments );
this.updateValue = this.updateValue.bind( this );
}
updateValue( newData ) {
const {
value,
onChange,
} = this.props;
const result = {
...value,
...newData,
};
// reset categories, tags and posts filters if post_type != 'post'
if ( 'post' === value.post_type && 'post' !== result.post_type ) {
result.categories = '';
result.tags = '';
result.posts = '';
}
onChange( result );
}
render() {
const {
value: {
posts_type = 'post',
categories = '',
tags = '',
exclude_categories = '',
exclude_tags = '',
formats = '',
posts = '',
offset = '',
orderby = 'date',
order = 'DESC',
time_frame = '',
taxonomy = '',
terms = '',
},
} = this.props;
return (
<Fragment>
<PostTypeSelectorControl
label={ __( 'Post Type' ) }
value={ posts_type }
onChange={ ( val ) => {
this.updateValue( {
posts_type: val,
} );
} }
/>
{ 'post' === posts_type ? (
<Fragment>
<CategoriesSelectorControl
label={ __( 'Filter by Categories' ) }
value={ categories }
onChange={ ( val ) => {
this.updateValue( {
categories: val,
} );
} }
/>
<TagsSelectorControl
label={ __( 'Filter by Tags' ) }
value={ tags }
onChange={ ( val ) => {
this.updateValue( {
tags: val,
} );
} }
/>
</Fragment>
) : '' }
{ 'post' === posts_type ? (
<Fragment>
<CategoriesSelectorControl
label={ __( 'Exclude Categories' ) }
value={ exclude_categories }
onChange={ ( val ) => {
this.updateValue( {
exclude_categories: val,
} );
} }
/>
<TagsSelectorControl
label={ __( 'Exclude Tags' ) }
value={ exclude_tags }
onChange={ ( val ) => {
this.updateValue( {
exclude_tags: val,
} );
} }
/>
</Fragment>
) : '' }
<PostFormatsSelectorControl
label={ __( 'Filter by Formats' ) }
value={ formats }
onChange={ ( val ) => {
this.updateValue( {
formats: val,
} );
} }
/>
<PostsSelectorControl
label={ __( 'Filter by Posts' ) }
value={ posts }
postType={ posts_type }
onChange={ ( val ) => {
this.updateValue( {
posts: val,
} );
} }
/>
<TextControl
label={ __( 'Offset' ) }
value={ offset }
onChange={ ( val ) => {
this.updateValue( {
offset: val,
} );
} }
/>
<SelectControl
label={ __( 'Order by' ) }
value={ orderby }
options={ [
{
label: __( 'Published Date' ),
value: 'date',
},
{
label: __( 'Modified Date' ),
value: 'modified',
},
{
label: __( 'Title' ),
value: 'title',
},
{
label: __( 'Random' ),
value: 'rand',
},
{
label: __( 'Views' ),
value: 'views',
},
{
label: __( 'Comment Count' ),
value: 'comment_count',
},
{
label: __( 'ID' ),
value: 'ID',
},
{
label: __( 'Custom' ),
value: 'post__in',
},
] }
onChange={ ( val ) => {
this.updateValue( {
orderby: val,
} );
} }
/>
{ 'views' === orderby ? (
<TextControl
label={ __( 'Filter by Time Frame' ) }
help={ __( 'Add period of posts in English. For example: «2 months», «14 days» or even «1 year»' ) }
value={ time_frame }
onChange={ ( val ) => {
this.updateValue( {
time_frame: val,
} );
} }
/>
) : '' }
<SelectControl
label={ __( 'Order' ) }
value={ order }
options={ [
{
label: __( 'Descending' ),
value: 'DESC',
},
{
label: __( 'Ascending' ),
value: 'ASC',
},
] }
onChange={ ( val ) => {
this.updateValue( {
order: val,
} );
} }
/>
<TextControl
label={ __( 'Filter by Taxonomy' ) }
value={ taxonomy }
onChange={ ( val ) => {
this.updateValue( {
taxonomy: val,
} );
} }
/>
<TextControl
label={ __( 'Filter by Terms' ) }
value={ terms }
onChange={ ( val ) => {
this.updateValue( {
terms: val,
} );
} }
/>
</Fragment>
);
}
}
@@ -0,0 +1,132 @@
/**
* Styles
*/
import './style.scss';
/**
* WordPress dependencies
*/
const {
Component,
} = wp.element;
const {
TextControl,
Button,
} = wp.components;
const allRadius = [ 'topLeft', 'topRight', 'bottomLeft', 'bottomRight'];
/**
* Component
*/
export default class ComponentRadius extends Component {
constructor() {
super( ...arguments );
this.updateRadius = this.updateRadius.bind( this );
}
/**
* Update number value.
*
* @param {String} name - number name.
* @param {String} prefix - type prefix.
* @param {String} suffix - responsive suffix.
* @param {String} val - new value.
*/
updateRadius( name, prefix = '', suffix = '', val ) {
const {
onChange,
} = this.props;
// TextControl return string value, we need to convert to int manually.
val = parseInt( val, 10 );
if ( Number.isNaN( val ) ) {
val = undefined;
}
const updateAttrs = {
[ prefix + name.charAt(0).toUpperCase() + name.slice(1) + suffix ]: val,
};
// Change all linked values.
if ( this.props.link ) {
allRadius.forEach( ( radius ) => {
updateAttrs[ prefix + radius.charAt(0).toUpperCase() + radius.slice(1) + suffix ] = val;
} )
}
onChange( updateAttrs );
}
render() {
const {
prefix = '',
suffix = '',
units = [ 'px', 'em' ],
onChange,
} = this.props;
return (
<div className="cnvs-component-radius">
<div className="cnvs-component-radius-units">
{ units.map( ( unit ) => {
return (
<Button
isPrimary={ unit === 'px' ? ! this.props.unit : this.props.unit === unit }
onClick={ () => {
onChange( {
[ prefix + 'Unit' + suffix ]: unit === 'px' ? '' : unit,
} );
} }
>
{ unit }
</Button>
);
} ) }
</div>
<div className="cnvs-component-radius-wrap">
{ allRadius.map( ( radius ) => {
let val = this.props[ radius ];
if ( typeof val == 'undefined' ) {
val = '';
}
return (
<TextControl
key={ radius }
type="number"
value={ val }
onChange={ ( val ) => this.updateRadius( radius, prefix, suffix, val ) }
autoComplete="off"
/>
);
} ) }
<Button
isDefault
isPrimary={ this.props.link }
onClick={ () => {
if ( this.props.link ) {
onChange( {
[ prefix + 'Link' + suffix ]: false,
} );
} else {
onChange( {
[ prefix + 'TopRight' + suffix ]: this.props.topLeft,
[ prefix + 'BottomLeft' + suffix ]: this.props.topLeft,
[ prefix + 'BottomRight' + suffix ]: this.props.topLeft,
[ prefix + 'Link' + suffix ]: true,
} );
}
} }
>
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M18.8379 8.23251L13.5352 13.5352C12.0703 15.0001 9.69727 15.0001 8.23242 13.5352L6.46484 11.7677L8.23242 10.0001L10 11.7677C10.4883 12.2547 11.2805 12.2559 11.7676 11.7677L17.0703 6.46493C17.5574 5.97727 17.5574 5.18442 17.0703 4.69672L15.3027 2.92914C14.8157 2.44207 14.0222 2.44207 13.5352 2.92914L11.6418 4.82247C10.7641 4.3061 9.76684 4.08454 8.7793 4.15106L11.7676 1.16157C13.2324 -0.302655 15.6067 -0.302655 17.0703 1.16157L18.8379 2.92914C20.3027 4.3934 20.3027 6.76829 18.8379 8.23251ZM8.35695 15.1783L6.46484 17.0704C5.97656 17.5587 5.18434 17.5575 4.69727 17.0704L2.92969 15.3028C2.44141 14.8158 2.44141 14.0235 2.92969 13.5352L8.23242 8.23251C8.71949 7.74544 9.51293 7.74544 10 8.23251L11.7676 10.0001L13.5352 8.23251L11.7676 6.46493C10.3027 5.00071 7.92969 5.00071 6.46484 6.46493L1.16211 11.7677C-0.302734 13.2325 -0.302734 15.6068 1.16211 17.0704L2.92969 18.838C4.39332 20.3028 6.76758 20.3028 8.23242 18.838L11.2207 15.8497C10.2332 15.9156 9.23828 15.6935 8.35695 15.1783Z" fill="currentColor"/></svg>
</Button>
</div>
</div>
);
}
}
@@ -0,0 +1,162 @@
/**
* Styles
*/
import './style.scss';
/**
* WordPress dependencies
*/
import classnames from 'classnames';
const {
canvasBreakpoints,
} = window;
const {
Component,
createRef,
} = wp.element;
const {
compose,
} = wp.compose;
const {
withSelect,
withDispatch,
} = wp.data;
/**
* Component
*/
class ComponentResponsiveDropdown extends Component {
constructor() {
super( ...arguments );
this.state = {
isOpened: false,
};
this.componentRef = createRef();
this.handleClickOutside = this.handleClickOutside.bind(this);
this.getButton = this.getButton.bind( this );
}
componentDidMount() {
document.addEventListener( 'mousedown', this.handleClickOutside );
}
componentWillUnmount() {
document.removeEventListener( 'mousedown', this.handleClickOutside );
}
/**
* Hide opened dropdown
*/
handleClickOutside( e ) {
if ( this.componentRef && this.componentRef.current && this.componentRef.current.contains( e.target ) ) {
return;
}
this.setState( {
isOpened: false,
} );
}
/**
* Get responsive button
*
* @param {string} name - breakpoint name.
* @param {function} onClick - click callback
*
* @return {JSX}
*/
getButton( name, onClick ) {
const {
breakpoint,
} = this.props;
name = name || 'desktop';
if ( typeof canvasBreakpoints[ name ] === 'undefined' ) {
return '';
}
const info = canvasBreakpoints[ name ];
return (
<button
key={ name }
className={ classnames(
'cnvs-component-breakpoints-dropdown-item',
breakpoint === name ? 'cnvs-component-breakpoints-dropdown-item-active' : ''
) }
onClick={ () => {
onClick( name === 'desktop' ? '' : name );
} }
dangerouslySetInnerHTML={ { __html: info.icon } }
/>
);
}
render() {
const {
breakpoint,
updateBreakpoint,
} = this.props;
const {
isOpened,
} = this.state;
return (
<div
className="cnvs-component-breakpoints"
ref={ this.componentRef }
>
{ this.getButton( breakpoint, () => {
this.setState( {
isOpened: ! isOpened,
} );
} ) }
{ isOpened ? (
<div className="cnvs-component-breakpoints-dropdown">
{ Object.keys( canvasBreakpoints ).map( ( name ) => {
return (
this.getButton( name, ( breakpoint ) => {
updateBreakpoint( breakpoint );
this.setState( {
isOpened: false,
} );
} )
);
} ) }
</div>
) : '' }
</div>
);
}
}
export default compose( [
withSelect( ( select ) => {
const {
getBreakpoint,
} = select( 'canvas/breakpoint' );
return {
breakpoint: getBreakpoint(),
};
} ),
withDispatch( ( dispatch ) => {
const {
updateBreakpoint,
} = dispatch( 'canvas/breakpoint' );
return {
updateBreakpoint,
};
} ),
] )( ComponentResponsiveDropdown );
@@ -0,0 +1,71 @@
/**
* WordPress dependencies
*/
const {
Component,
Fragment,
} = wp.element;
const {
compose,
} = wp.compose;
const {
withSelect,
withDispatch,
} = wp.data;
import ComponentResponsiveDropdown from '../responsive-dropdown';
/**
* Component
*/
class ComponentResponsiveWrapper extends Component {
constructor() {
super( ...arguments );
}
render() {
const {
breakpoint,
children,
} = this.props;
const data = {
responsiveSuffix: '',
breakpoint,
ComponentResponsiveDropdown,
};
if ( breakpoint && 'desktop' !== breakpoint ) {
data.responsiveSuffix = '_' + breakpoint;
}
return (
<Fragment key={ `responsive-wrapper-${ breakpoint }` }>
{ children( data ) }
</Fragment>
);
}
}
export default compose( [
withSelect( ( select ) => {
const {
getBreakpoint,
} = select( 'canvas/breakpoint' );
return {
breakpoint: getBreakpoint(),
};
} ),
withDispatch( ( dispatch ) => {
const {
updateBreakpoint,
} = dispatch( 'canvas/breakpoint' );
return {
updateBreakpoint,
};
} ),
] )( ComponentResponsiveWrapper );
@@ -0,0 +1,163 @@
/**
* Styles
*/
import './style.scss';
/**
* WordPress dependencies
*/
import classnames from 'classnames';
const {
canvasSchemes,
} = window;
const {
Component,
createRef,
} = wp.element;
const {
compose,
} = wp.compose;
const {
withSelect,
withDispatch,
} = wp.data;
/**
* Component
*/
class ComponentSchemeDropdown extends Component {
constructor() {
super( ...arguments );
this.state = {
isOpened: false,
};
this.componentRef = createRef();
this.handleClickOutside = this.handleClickOutside.bind(this);
this.getButton = this.getButton.bind( this );
}
componentDidMount() {
document.addEventListener( 'mousedown', this.handleClickOutside );
}
componentWillUnmount() {
document.removeEventListener( 'mousedown', this.handleClickOutside );
}
/**
* Hide opened dropdown
*/
handleClickOutside( e ) {
if ( this.componentRef && this.componentRef.current && this.componentRef.current.contains( e.target ) ) {
return;
}
this.setState( {
isOpened: false,
} );
}
/**
* Get responsive button
*
* @param {string} name - scheme name.
* @param {function} onClick - click callback
*
* @return {JSX}
*/
getButton( name, onClick ) {
const {
scheme,
} = this.props;
name = name || 'default';
if ( typeof canvasSchemes[ name ] === 'undefined' ) {
return '';
}
const info = canvasSchemes[ name ];
return (
<button
key={ name }
className={ classnames(
'cnvs-component-schemes-dropdown-item',
scheme === name ? 'cnvs-component-schemes-dropdown-item-active' : ''
) }
onClick={ () => {
onClick( name === 'default' ? '' : name );
} }
dangerouslySetInnerHTML={ { __html: info.icon } }
/>
);
}
render() {
const {
scheme,
updateScheme,
} = this.props;
const {
isOpened,
} = this.state;
return (
<div
className="cnvs-component-schemes"
ref={ this.componentRef }
>
{ this.getButton( scheme, () => {
this.setState( {
isOpened: ! isOpened,
} );
} ) }
{ isOpened ? (
<div className="cnvs-component-schemes-dropdown">
{ Object.keys( canvasSchemes ).map( ( name ) => {
return (
this.getButton( name, ( scheme ) => {
updateScheme( scheme );
this.setState( {
isOpened: false,
} );
} )
);
} ) }
</div>
) : '' }
</div>
);
}
}
export default compose( [
withSelect( ( select ) => {
const {
getScheme,
} = select( 'canvas/scheme' );
return {
scheme: getScheme(),
};
} ),
withDispatch( ( dispatch ) => {
const {
updateScheme,
} = dispatch( 'canvas/scheme' );
return {
updateScheme,
};
} ),
] )( ComponentSchemeDropdown );
@@ -0,0 +1,71 @@
/**
* WordPress dependencies
*/
const {
Component,
Fragment,
} = wp.element;
const {
compose,
} = wp.compose;
const {
withSelect,
withDispatch,
} = wp.data;
import ComponentSchemeDropdown from '../scheme-dropdown';
/**
* Component
*/
class ComponentSchemeWrapper extends Component {
constructor() {
super(...arguments);
}
render() {
const {
scheme,
children,
} = this.props;
const data = {
schemeSuffix: '',
scheme,
ComponentSchemeDropdown,
};
if (scheme && 'default' !== scheme) {
data.schemeSuffix = '_' + scheme;
}
return (
<Fragment key={`scheme-wrapper-${scheme}`}>
{children(data)}
</Fragment>
);
}
}
export default compose([
withSelect((select) => {
const {
getScheme,
} = select('canvas/scheme');
return {
scheme: getScheme(),
};
}),
withDispatch((dispatch) => {
const {
updateScheme,
} = dispatch('canvas/scheme');
return {
updateScheme,
};
}),
])(ComponentSchemeWrapper);
@@ -0,0 +1,46 @@
/**
* WordPress dependencies
*/
const { useMemo } = wp.element;
const { withSelect } = wp.data;
/**
* Internal dependencies
*/
import ServerSideRender from './server-side-render';
/**
* Constants
*/
const EMPTY_OBJECT = {};
export default withSelect(
( select ) => {
const coreEditorSelect = select( 'core/editor' );
if ( coreEditorSelect ) {
const currentPostId = coreEditorSelect.getCurrentPostId();
if ( currentPostId ) {
return {
currentPostId,
};
}
}
return EMPTY_OBJECT;
}
)(
( { urlQueryArgs = EMPTY_OBJECT, currentPostId, ...props } ) => {
const newUrlQueryArgs = useMemo( () => {
if ( ! currentPostId ) {
return urlQueryArgs;
}
return {
post_id: currentPostId,
...urlQueryArgs,
};
}, [ currentPostId, urlQueryArgs ] );
return (
<ServerSideRender urlQueryArgs={ newUrlQueryArgs } { ...props } />
);
}
);
@@ -0,0 +1,210 @@
/*
* This is a copy of Gutenberg component https://github.com/WordPress/gutenberg/blob/master/packages/server-side-render/src/server-side-render.js
* With the changes in our component:
* - callbacks before and after new content render
* - slightly different rendering process - don't remove previously rendered content. So, we will not see page jumps after every attribute change.
*/
/**
* External dependencies
*/
import classnames from 'classnames';
const { isEqual, debounce } = window.lodash;
/**
* Internal dependencies
*/
import './style.scss';
/**
* WordPress dependencies
*/
const {
Component,
RawHTML,
} = wp.element;
const { __, sprintf } = wp.i18n;
const apiFetch = wp.apiFetch;
const { addQueryArgs } = wp.url;
const {
Placeholder,
Spinner,
} = wp.components;
const {
doAction,
} = wp.hooks;
export function rendererPath( block, attributes = null, urlQueryArgs = {} ) {
return addQueryArgs( `/wp/v2/canvas-renderer/${ block }`, {
context: 'edit',
...( null !== attributes ? { attributes } : {} ),
...urlQueryArgs,
} );
}
export class ServerSideRender extends Component {
constructor( props ) {
super( props );
this.state = {
response: null,
prevResponse: null,
};
}
componentDidMount() {
this.isStillMounted = true;
this.fetch( this.props );
// Only debounce once the initial fetch occurs to ensure that the first
// renders show data as soon as possible.
this.fetch = debounce( this.fetch, 500 );
}
componentWillUnmount() {
this.isStillMounted = false;
}
componentDidUpdate( prevProps ) {
const prevAttributes = prevProps.attributes;
const curAttributes = this.props.attributes;
if ( ! isEqual( prevAttributes, curAttributes ) ) {
this.fetch( this.props );
}
}
fetch( props ) {
if ( ! this.isStillMounted ) {
return;
}
const {
block,
attributes = null,
urlQueryArgs = {},
onBeforeChange = () => {},
onChange = () => {},
} = props;
if ( null !== this.state.response ) {
this.setState( {
response: null,
prevResponse: this.state.response,
} );
}
const path = rendererPath( block );
// Store the latest fetch request so that when we process it, we can
// check if it is the current request, to avoid race conditions on slow networks.
const fetchRequest = this.currentFetchRequest = apiFetch( { method: 'POST', path, data: { attributes: attributes } } )
.then( ( response ) => {
if ( this.isStillMounted && fetchRequest === this.currentFetchRequest && response ) {
onBeforeChange();
doAction( 'canvas.components.serverSideRender.onBeforeChange', this.props );
this.setState( {
response: response.rendered,
prevResponse: null,
}, () => {
onChange();
doAction( 'canvas.components.serverSideRender.onChange', this.props );
} );
}
} )
.catch( ( error ) => {
if ( this.isStillMounted && fetchRequest === this.currentFetchRequest ) {
onBeforeChange();
doAction( 'canvas.components.serverSideRender.onBeforeChange', this.props );
this.setState( {
response: {
error: true,
errorMsg: error.message,
},
prevResponse: null,
}, () => {
onChange();
doAction( 'canvas.components.serverSideRender.onChange', this.props );
} );
}
} );
return fetchRequest;
}
render() {
const {
response,
prevResponse,
} = this.state;
let { className } = this.props;
className = classnames(
className,
'cnvs-component-server-side-render'
);
if ( response === '' ) {
return (
<Placeholder
className={ className }
>
{ __( 'Block rendered as empty.' ) }
</Placeholder>
);
} else if ( ! response && prevResponse ) {
className = classnames(
className,
'cnvs-component-server-side-render-loading'
);
return (
<div className={ className }>
<Spinner />
<RawHTML
key="html"
className="cnvs-component-server-side-render-content"
>
{ prevResponse }
</RawHTML>
</div>
);
} else if ( ! response ) {
return (
<Placeholder
className={ className }
>
<Spinner />
</Placeholder>
);
} else if ( response.error ) {
// translators: %s: error message describing the problem
const errorMessage = sprintf( __( 'Error loading block: %s' ), response.errorMsg );
return (
<Placeholder
className={ className }
>
{ errorMessage }
</Placeholder>
);
}
return (
<div className={ className }>
<RawHTML
key="html"
className="cnvs-component-server-side-render-content"
>
{ response }
</RawHTML>
</div>
);
}
}
export default ServerSideRender;
@@ -0,0 +1,133 @@
/**
* Styles
*/
import './style.scss';
/**
* WordPress dependencies
*/
const {
Component,
} = wp.element;
const {
TextControl,
Button,
} = wp.components;
const allSpacings = [ 'top', 'bottom', 'left', 'right' ];
/**
* Component
*/
export default class ComponentSpacings extends Component {
constructor() {
super( ...arguments );
this.updateSpacing = this.updateSpacing.bind( this );
}
/**
* Update spacing value.
*
* @param {String} name - spacing name.
* @param {String} prefix - type prefix.
* @param {String} suffix - responsive suffix.
* @param {String} val - new value.
*/
updateSpacing( name, prefix = '', suffix = '', val ) {
const {
onChange,
} = this.props;
// TextControl return string value, we need to convert to int manually.
val = parseInt( val, 10 );
if ( Number.isNaN( val ) ) {
val = undefined;
}
const updateAttrs = {
[ prefix + name.charAt(0).toUpperCase() + name.slice(1) + suffix ]: val,
};
// Change all linked values.
if ( this.props.link ) {
allSpacings.forEach( ( spacing ) => {
updateAttrs[ prefix + spacing.charAt(0).toUpperCase() + spacing.slice(1) + suffix ] = val;
} )
}
onChange( updateAttrs );
}
render() {
const {
prefix = '',
suffix = '',
units = [ 'px', 'em' ],
onChange,
} = this.props;
return (
<div className="cnvs-component-spacings">
<div className="cnvs-component-spacings-units">
{ units.map( ( unit ) => {
return (
<Button
isPrimary={ unit === 'px' ? ! this.props.unit : this.props.unit === unit }
onClick={ () => {
onChange( {
[ prefix + 'Unit' + suffix ]: unit === 'px' ? '' : unit,
} );
} }
>
{ unit }
</Button>
);
} ) }
</div>
<div className="cnvs-component-spacings-wrap">
{ allSpacings.map( ( spacing ) => {
let val = this.props[ spacing ];
if ( typeof val == 'undefined' ) {
val = '';
}
return (
<TextControl
key={ spacing }
type="number"
label={ spacing }
value={ val }
onChange={ ( val ) => this.updateSpacing( spacing, prefix, suffix, val ) }
autoComplete="off"
/>
);
} ) }
<Button
isDefault
isPrimary={ this.props.link }
onClick={ () => {
if ( this.props.link ) {
onChange( {
[ prefix + 'Link' + suffix ]: false,
} );
} else {
onChange( {
[ prefix + 'Bottom' + suffix ]: this.props.top,
[ prefix + 'Left' + suffix ]: this.props.top,
[ prefix + 'Right' + suffix ]: this.props.top,
[ prefix + 'Link' + suffix ]: true,
} );
}
} }
>
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M18.8379 8.23251L13.5352 13.5352C12.0703 15.0001 9.69727 15.0001 8.23242 13.5352L6.46484 11.7677L8.23242 10.0001L10 11.7677C10.4883 12.2547 11.2805 12.2559 11.7676 11.7677L17.0703 6.46493C17.5574 5.97727 17.5574 5.18442 17.0703 4.69672L15.3027 2.92914C14.8157 2.44207 14.0222 2.44207 13.5352 2.92914L11.6418 4.82247C10.7641 4.3061 9.76684 4.08454 8.7793 4.15106L11.7676 1.16157C13.2324 -0.302655 15.6067 -0.302655 17.0703 1.16157L18.8379 2.92914C20.3027 4.3934 20.3027 6.76829 18.8379 8.23251ZM8.35695 15.1783L6.46484 17.0704C5.97656 17.5587 5.18434 17.5575 4.69727 17.0704L2.92969 15.3028C2.44141 14.8158 2.44141 14.0235 2.92969 13.5352L8.23242 8.23251C8.71949 7.74544 9.51293 7.74544 10 8.23251L11.7676 10.0001L13.5352 8.23251L11.7676 6.46493C10.3027 5.00071 7.92969 5.00071 6.46484 6.46493L1.16211 11.7677C-0.302734 13.2325 -0.302734 15.6068 1.16211 17.0704L2.92969 18.838C4.39332 20.3028 6.76758 20.3028 8.23242 18.838L11.2207 15.8497C10.2332 15.9156 9.23828 15.6935 8.35695 15.1783Z" fill="currentColor"/></svg>
</Button>
</div>
</div>
);
}
}
@@ -0,0 +1,185 @@
/**
* External dependencies
*/
import ReactSelect from 'react-select';
import { debounce } from 'throttle-debounce';
/**
* WordPress dependencies
*/
const {
Component,
} = wp.element;
const {
withSelect,
} = wp.data;
const {
BaseControl,
} = wp.components;
const cachedTags = {};
function maybeCache( newTags ) {
if ( ! newTags || ! newTags.length ) {
return;
}
if ( ! cachedTags ) {
cachedTags = {};
}
newTags.forEach( ( postData ) => {
if ( ! cachedTags[ postData.id ] ) {
cachedTags[ postData.id ] = postData;
}
} );
}
/**
* Component
*/
class ComponentTagsSelectorControl extends Component {
constructor() {
super( ...arguments );
this.state = {
searchTerm: '',
};
this.updateSearchTerm = debounce( 300, this.updateSearchTerm.bind( this ) );
}
/**
* Update search term with debounce.
*
* @param {String} search - search term.
*/
updateSearchTerm( search ) {
this.setState( {
searchTerm: search,
} );
}
render() {
const {
value,
label,
help,
onChange,
allTags,
findTags,
} = this.props;
const {
searchTerm,
} = this.state;
const foundTags = findTags( searchTerm ) || [];
return (
<BaseControl
label={ label }
help={ help }
>
<ReactSelect
options={
foundTags.map( ( catData ) => {
return {
value: catData.slug,
label: catData.name,
};
} )
}
value={ ( () => {
let result = value ? value.split(',') : [];
if ( result && result.length ) {
result = result.map( ( name ) => {
let thisData = {
value: name,
label: name,
};
// get label from tags list
for (var id in allTags ) {
if ( name === allTags[id].slug ) {
thisData.label = allTags[id].name;
}
}
return thisData;
} );
return result;
}
return [];
} )() }
isMulti
name="filter-by-categories"
className="basic-multi-select"
classNamePrefix="select"
components={ {
IndicatorSeparator: () => null,
ClearIndicator: () => null,
} }
onInputChange={ ( val ) => {
this.updateSearchTerm( val );
} }
onChange={ ( val ) => {
let result = '';
let slug = '';
if ( val ) {
val.forEach( ( catData ) => {
result += slug + catData.value;
slug = ',';
} );
}
onChange( result );
} }
/>
</BaseControl>
);
}
}
export default withSelect( ( select, props, a, b, c ) => {
const {
getEntityRecords,
} = select( 'core' );
const {
value,
} = props;
let slugs = value ? value.split(',') : [];
// find non-cached tags and try to retrieve.
slugs = slugs.filter( ( slug ) => {
return ! cachedTags || ! cachedTags[ slug ];
} );
if ( slugs && slugs.length ) {
const newTags = getEntityRecords( 'taxonomy', 'post_tag', {
slug: slugs,
per_page: 100,
} );
maybeCache( newTags );
}
return {
findTags( search = '' ) {
const searchTags = getEntityRecords( 'taxonomy', 'post_tag', {
search,
per_page: 20,
} );
maybeCache( searchTags );
return searchTags;
},
allTags: cachedTags || {},
};
})( ComponentTagsSelectorControl );
File diff suppressed because one or more lines are too long
@@ -0,0 +1,510 @@
/**
* Internal dependencies
*/
import './style.scss';
import FieldsRender from '../components/fields-render';
import FieldsCSSOutput from '../components/fields-css-output';
import ServerSideRender from '../components/server-side-render';
import ImageSelector from '../components/image-selector';
import getParentBlock from '../utils/get-parent-block';
/**
* WordPress dependencies
*/
const {
__,
} = wp.i18n;
const {
registerBlockType,
} = wp.blocks;
const {
Component,
Fragment,
RawHTML,
} = wp.element;
const {
BaseControl,
Placeholder,
PanelBody,
Disabled,
Notice,
} = wp.components;
const {
InspectorControls,
} = wp.blockEditor;
const {
applyFilters,
} = wp.hooks;
const {
withSelect,
} = wp.data;
const {
blocks,
} = window.pk_custom_blocks_localize;
const { select, subscribe } = wp.data;
const allLocations = {
root: __('Block editor root'),
'section-wide': __('Fullwidth section'),
'section-full': __('Fullwidth section with alignment set to "Full"'),
'section-content': __('The content part of a sidebar section'),
'section-sidebar': __('The sidebar part of a sidebar section'),
};
// Value, that will not be saved in field attribute.
// Used in columns block.
const CNVS_PREVENT_UPDATE = 'CNVS_PREVENT_UPDATE';
// Template Switcher.
class TemplateSwitcher {
constructor() {
this.template = null;
}
init() {
subscribe(() => {
const newTemplate = select('core/editor').getEditedPostAttribute( 'template' );
if ( newTemplate !== this.template ) {
this.template = newTemplate;
this.changeTemplate();
}
});
}
changeTemplate() {
let blocks = wp.data.select('core/block-editor').getBlocks();
blocks.forEach((el) => {
wp.data.dispatch('core/block-editor').updateBlock(el.clientId, { attributes: {} });
});
}
}
new TemplateSwitcher().init();
/**
* Get block icons and convert string `<svg...` to react component
*
* @param {String} { icon } - icon string
* @return {String|JSX}
*/
function getBlockIcon({ icon }) {
if ('string' === typeof icon && '<svg' === icon.trim().substr(0, 4)) {
icon = <RawHTML>{icon}</RawHTML>;
}
return icon || '';
}
/**
* Get block Edit class.
*
* @param {Object} blockData block data
* @return {Class}
*/
function getBlockEdit(blockData) {
const {
name,
title,
fields,
sections,
layouts,
} = blockData;
const icon = getBlockIcon(blockData);
class CustomBlockEdit extends Component {
constructor() {
super(...arguments);
this.maybeUpdateLocationAttribute = this.maybeUpdateLocationAttribute.bind(this);
this.maybeSetDefaultLayout = this.maybeSetDefaultLayout.bind(this);
this.getAllowedLocations = this.getAllowedLocations.bind(this);
this.getAllowedLocationsNotice = this.getAllowedLocationsNotice.bind(this);
this.isAllowed = this.isAllowed.bind(this);
this.getLayoutSelector = this.getLayoutSelector.bind(this);
this.maybeUpdateLocationAttribute(true);
}
componentDidMount() {
this.maybeSetDefaultLayout();
this.maybeUpdateLocationAttribute();
}
componentDidUpdate() {
this.maybeUpdateLocationAttribute();
}
/**
* Maybe update location attribute.
*/
maybeUpdateLocationAttribute(force) {
if (this.props.location !== this.props.attributes.canvasLocation) {
if (force) {
this.props.attributes.canvasLocation = this.props.location;
}
this.props.setAttributes({
canvasLocation: this.props.location,
});
}
}
/**
* Maybe set default layout, when available only 1 layout.
*/
maybeSetDefaultLayout() {
const {
attributes,
setAttributes,
} = this.props;
const {
layout,
} = attributes;
const layoutsCount = layouts ? Object.keys(layouts).length : 0;
if (1 === layoutsCount) {
const isLayoutDifferent = Object.keys(layouts)[0] !== layout;
if (isLayoutDifferent) {
setAttributes({
layout: Object.keys(layouts)[0],
});
}
}
}
/**
* Get array with all allowed locations.
*
* @return {Array}
*/
getAllowedLocations() {
const {
attributes,
} = this.props;
const {
layout,
} = attributes;
// get locations from the current layout
// and from the block settings.
return [
...(layouts && layouts[layout] && layouts[layout].location ? layouts[layout].location : []),
...blockData.location,
];
}
/**
* Get notice about allowed locations.
*
* @param {Array} checkLocations array of locations to check. If empty, use locations from the block settings and layout
* @return {JSX}
*/
getAllowedLocationsNotice(checkLocations) {
// get locations from the current layout
// and from the block settings.
if (!checkLocations) {
checkLocations = this.getAllowedLocations();
}
return (
<Fragment>
<p>{__('The block output is allowed in following locations only:')}</p>
<ul>
{checkLocations.map((locationName) => {
return (
<li key={`location-${locationName}`}>
{allLocations[locationName] || locationName}
</li>
);
})}
</ul>
</Fragment>
);
}
/**
* Check if block is allowed in current location.
*
* @param {Array} checkLocations array of locations to check. If empty, use locations from the block settings and layout
* @return {boolean}
*/
isAllowed(checkLocations) {
const {
location,
} = this.props;
let result = true;
// get locations from the current layout
// and from the block settings.
if (!checkLocations) {
checkLocations = this.getAllowedLocations();
}
if (checkLocations && checkLocations.length) {
result = false;
checkLocations.forEach((locationName) => {
if (location === locationName) {
result = true;
}
});
}
return result;
}
/**
* Returns layout selector.
*
* @return {JSX} ImageSelector.
*/
getLayoutSelector() {
const {
setAttributes,
} = this.props;
const {
layout,
} = this.props.attributes;
const items = Object.keys(layouts).map((layoutName) => {
const layoutData = layouts[layoutName];
const isDisabled = !this.isAllowed(layoutData.location);
return {
content: <RawHTML>{layoutData.icon}</RawHTML>,
value: layoutName,
label: layoutData.name,
isDisabled,
disabledNotice: isDisabled ? this.getAllowedLocationsNotice(layoutData.location) : '',
};
});
return (
<ImageSelector
value={layout}
onChange={(val) => {
setAttributes({
layout: val,
});
}}
items={items}
/>
);
}
render() {
const {
attributes,
setAttributes,
} = this.props;
const {
layout,
canvasClassName,
} = attributes;
const isLayoutsAvailable = layouts && Object.keys(layouts).length > 1;
// layout selector.
if (isLayoutsAvailable && !layout) {
return (
<Placeholder
className="canvas-component-custom-blocks-placeholder"
icon={icon}
label={title}
instructions={__('Select the block layout.')}
>
{this.getLayoutSelector()}
</Placeholder>
);
}
// Block render if all checks passed.
const blockRender = applyFilters('canvas.customBlock.editRender', (
<Disabled>
<ServerSideRender
block={name}
blockProps={this.props}
attributes={attributes}
/>
</Disabled>
), this.props);
return (
<div className="canvas-component-custom-blocks">
{this.isAllowed() ? (
blockRender
) : (
<Placeholder
className="canvas-component-custom-blocks-placeholder"
icon={icon}
label={title}
>
<Notice status="warning" isDismissible={false}>
{this.getAllowedLocationsNotice()}
</Notice>
</Placeholder>
)}
{fields ? (
<Fragment>
<FieldsCSSOutput
selector={canvasClassName ? `.${canvasClassName}` : false}
fields={fields}
attributes={attributes}
/>
<InspectorControls>
{isLayoutsAvailable ? (
<PanelBody
title={__('Layout')}
initialOpen={ false }
>
<BaseControl>
{this.getLayoutSelector()}
</BaseControl>
</PanelBody>
) : ''}
<FieldsRender
fields={fields}
sections={sections}
attributes={attributes}
blockProps={this.props}
onChange={(key, val) => {
val = applyFilters('canvas.customBlock.onFieldChange', val, key, this.props);
if ( CNVS_PREVENT_UPDATE !== val ) {
setAttributes({ [key]: val });
}
}}
/>
</InspectorControls>
</Fragment>
) : ''}
</div>
);
}
}
/*
* Prepare location type name of the current block.
*
* Available types:
* - root - block inserted in root
* - section-wide - block inserted in Section block with layout = wide
* - section-full - block inserted in Section block with layout = full
* - section-content - block inserted in Section block inside Content column
* - block inserted in Row block inside column with size [5-11]
* - section-sidebar - block inserted in Section block inside Sidebar column
* - block inserted in Row block inside column with size [1-4]
*/
return withSelect((select, ownProps) => {
const {
getBlockHierarchyRootClientId,
getBlock,
} = select('core/block-editor');
const rootBlock = getBlock(getBlockHierarchyRootClientId(ownProps.clientId));
let parentBlock = getParentBlock(rootBlock, ownProps);
let isRoot = parentBlock && parentBlock.clientId === ownProps.clientId;
// Skip block `core/group` from this check as we can use it for Query Settings.
if ( 'core/group' === parentBlock.name && ! isRoot ) {
const postGroupId = parentBlock.clientId;
parentBlock = getParentBlock(rootBlock, parentBlock);
isRoot = parentBlock && parentBlock.clientId === postGroupId;
}
let location = 'default';
// root
if ( isRoot ) {
location = 'root';
// inside section content
} else if (parentBlock && 'canvas/section-content' === parentBlock.name) {
const sectionBlock = getParentBlock(rootBlock, parentBlock);
if ('full' === sectionBlock.attributes.layout) {
if ('full' === sectionBlock.attributes.layoutAlign) {
location = 'section-full';
} else {
location = 'section-wide';
}
} else if ('with-sidebar' === sectionBlock.attributes.layout) {
location = 'section-content';
}
// inside section sidebar
} else if (parentBlock && 'canvas/section-sidebar' === parentBlock.name) {
location = 'section-sidebar';
// inside row block
} else if (parentBlock && 'canvas/column' === parentBlock.name) {
if (parentBlock.attributes.size < 5) {
location = 'section-sidebar';
} else {
location = 'section-content';
}
}
return {
location,
};
})(CustomBlockEdit);
}
/**
* Register Custom Blocks
*/
jQuery(() => {
if (blocks && blocks.length) {
blocks.forEach((blockData) => {
let {
supports = {},
} = blockData;
if ( 'canvas/section' === blockData.name && 'page' !== canvasLocalize.postType ) {
return;
}
const resultBlockData = applyFilters( 'canvas.customBlock.registerData', {
...blockData,
supports,
icon: getBlockIcon(blockData),
edit: getBlockEdit(blockData),
save() {
// Render in PHP.
return null;
},
});
// Register block.
registerBlockType(blockData.name, resultBlockData);
// Register block style.
if (blockData.styles && blockData.length) {
blockData.styles.forEach((styleData) => {
registerBlockStyle(blockData.name, styleData);
});
}
});
}
});
@@ -0,0 +1,930 @@
<?php
/**
* Custom blocks registration.
*
* @package Canvas
*/
if ( ! class_exists( 'CNVS_Gutenberg_Blocks_Registration' ) ) {
class CNVS_Gutenberg_Blocks_Registration {
/**
* Contains all custom blocks.
*
* @var array
*/
public $all_blocks = array();
function __construct() {
add_action( 'init', array( $this, 'register_block_type' ), 12 );
add_action( 'canvas_blocks_dynamic_css', array( $this, 'blocks_dynamic_css' ), 10, 2 );
add_filter( 'canvas_block_convert_fields_to_attributes', array( $this, 'convert_fields_to_attributes' ) );
add_filter( 'canvas_block_prepare_server_render_attributes', array( $this, 'prepare_server_render_attributes' ), 10, 2 );
}
/**
* Get default settings.
*
* @return array
*/
public function get_custom_blocks_default_settings() {
return apply_filters(
'canvas_register_block_type_default_settings', array(
'name' => '',
'title' => '',
'description' => '',
'category' => '',
'keywords' => array(),
'icon' => '',
'supports' => array(
'className' => true,
'anchor' => true,
),
'styles' => array(),
'location' => array(),
'layouts' => array(),
'sections' => array(),
'fields' => array(),
'template' => '',
'style' => '',
'script' => '',
'editor_style' => '',
'editor_script' => '',
)
);
}
/**
* Get custom blocks array.
*
* @return array
*/
public function get_custom_blocks() {
if ( ! empty( $this->all_blocks ) ) {
return $this->all_blocks;
}
$blocks = apply_filters( 'canvas_register_block_type', array() );
$default_settings = $this->get_custom_blocks_default_settings();
// add default settings to all blocks.
foreach ( $blocks as $k => $block ) {
$block['sections'] = apply_filters( 'canvas_block_sections_' . $block['name'], $block['sections'] );
$block['fields'] = apply_filters( 'canvas_block_fields_' . $block['name'], $block['fields'] );
$block['layouts'] = apply_filters( 'canvas_block_layouts_' . $block['name'], $block['layouts'] );
// prepare layouts fields.
if ( isset( $block['layouts'] ) && ! empty( $block['layouts'] ) ) {
$layouts = $this->get_layouts_fields( $block['name'], $block['layouts'] );
// hide default fields if layout selected.
foreach ( $block['layouts'] as $layout => $data ) {
if ( ! isset( $data['hide_fields'] ) ) {
continue;
}
// check all default fields.
foreach ( $block['fields'] as $f => $field ) {
if ( ! isset( $field['key'] ) ) {
continue;
}
if ( ! in_array( $field['key'], $data['hide_fields'] ) ) {
continue;
}
if ( ! isset( $block['fields'][ $f ]['active_callback'] ) ) {
$block['fields'][ $f ]['active_callback'] = array();
}
// hide default field.
$block['fields'][ $f ]['active_callback'][] = array(
'field' => 'layout',
'operator' => '!==',
'value' => $layout,
);
}
}
// Set sections.
$block['sections'] = array_merge(
$block['sections'],
$layouts['sections']
);
// Multisort sections.
if ( is_array( $block['sections'] ) && $block['sections'] ) {
foreach ( $block['sections'] as $key => $value ) {
if ( is_string( $value ) ) {
$block['sections'][ $key ] = array(
'title' => $value,
'priority' => 10,
);
}
}
$priority = array_column( $block['sections'], 'priority' );
array_multisort( $priority, SORT_ASC, $block['sections'] );
}
// Set fields.
$block['fields'] = array_merge(
$block['fields'],
$layouts['fields']
);
}
$blocks[ $k ] = array_merge(
$default_settings,
$block
);
}
$this->all_blocks = $blocks;
return $this->all_blocks;
}
/**
* Get custom block data.
*
* @param string $name block name.
* @return array
*/
public function get_custom_block( $name ) {
$blocks = $this->get_custom_blocks();
$result = false;
foreach ( $blocks as $block ) {
if ( $name === $block['name'] ) {
$result = $block;
}
}
return $result;
}
/**
* Parse layouts and prepare fields for block.
*
* For example, if we have layouts with name 'grid' and fields inside with the name 'columns'
* will be added field with the name 'layout_grid_columns'
*
* @param string $block_name block name.
* @param array $layouts block layouts.
* @return array fields and sections used in the block.
*/
public function get_layouts_fields( $block_name, $layouts ) {
$block_sections = array();
$block_fields = array();
// look at all layouts.
foreach ( $layouts as $layout => $data ) {
$layout_prefix = 'layout_' . $layout . '_';
// add new sections.
if ( isset( $data['sections'] ) && ! empty( $data['sections'] ) ) {
$block_sections = array_merge( $block_sections, $data['sections'] );
}
// look at all layout fields.
if ( isset( $data['fields'] ) && is_array( $data['fields'] ) ) {
foreach ( $data['fields'] as $field ) {
if ( ! isset( $field['key'] ) ) {
continue;
}
// convert field names.
$field['key'] = $layout_prefix . $field['key'];
// process recursive.
if ( isset( $field['active_callback'] ) ) {
$field['active_callback'] = $this->process_recursive( $field['active_callback'], $layout_prefix );
}
// show fields only for selected layout.
$field['active_callback'][] = array(
'field' => 'layout',
'operator' => '===',
'value' => $layout,
);
$block_fields[] = $field;
}
}
}
$block_fields = apply_filters( 'canvas_block_layouts_fields_' . $block_name, $block_fields );
$block_sections = apply_filters( 'canvas_block_layouts_sections_' . $block_name, $block_sections );
return array(
'sections' => $block_sections,
'fields' => $block_fields,
);
}
/**
* Add prefixes to field names in active_callback array.
*
* @param array $fields fields array from 'active_callback'.
* @param string $prefix prefix.
* @return array
*/
public function process_recursive( $fields, $prefix ) {
$changed_fields = array();
foreach ( $fields as $key => $item ) {
if ( isset( $item['field'] ) ) {
// Add prefix to new item.
$item['field'] = str_replace( '$#', $prefix, $item['field'] );
$changed_fields[] = $item;
} elseif ( is_array( $item ) ) {
$changed_fields[] = $this->process_recursive( $item, $prefix );
}
}
return $changed_fields;
}
/**
* Register custom blocks.
*/
public function register_block_type() {
if ( ! function_exists( 'register_block_type' ) ) {
return;
}
$blocks = $this->get_custom_blocks();
$schemes = cnvs_gutenberg()->get_schemes_data();
$breakpoints = cnvs_gutenberg()->get_breakpoints_data();
if ( empty( $blocks ) ) {
return;
}
$editor_script_deps = array( 'wp-blocks', 'wp-components', 'wp-element', 'wp-i18n', 'wp-editor' );
// custom editor scripts.
foreach ( $blocks as $block ) {
if ( isset( $block['editor_script'] ) && $block['editor_script'] ) {
$editor_script_deps[] = $block['editor_script'];
}
}
// Script.
wp_register_script(
'canvas-custom-blocks-editor-script',
CNVS_URL . 'gutenberg/custom-blocks/index.js',
$editor_script_deps,
filemtime( CNVS_PATH . 'gutenberg/custom-blocks/index.js' )
);
// Add additional data to scripts.
wp_localize_script(
'canvas-custom-blocks-editor-script', 'pk_custom_blocks_localize', array(
'blocks' => $blocks,
)
);
// Register all blocks.
foreach ( $blocks as $block ) {
// Default attributes, that will be used in render callback.
$attributes = array(
'canvasBlockName' => array(
'type' => 'string',
'default' => $block['name'],
),
'canvasClassName' => array(
'type' => 'string',
),
'canvasLocation' => array(
'type' => 'string',
),
);
// Add support block widget.
$attributes['__internalWidgetId'] = array(
'type' => 'string',
);
// Supports.
if ( isset( $block['supports'] ) && ! empty( $block['supports'] ) ) {
foreach ( $block['supports'] as $support => $is_true ) {
if ( $is_true ) {
switch ( $support ) {
case 'className':
$attributes['className'] = array(
'type' => 'string',
);
break;
case 'anchor':
$attributes['anchor'] = array(
'type' => 'string',
);
break;
case 'align':
$attributes['align'] = array(
'type' => 'string',
);
case 'canvasBackgroundImage':
$background_attrs = array();
foreach ( $breakpoints as $name => $breakpoint ) {
$suffix = '';
if ( $name && 'desktop' !== $name ) {
$suffix = '_' . $name;
}
// background-image.
$background_attrs[ 'backgroundImage' . $suffix ] = array(
'type' => 'object',
);
// background-position.
$background_attrs[ 'backgroundPosition' . $suffix ] = array(
'type' => 'string',
);
// background-position-unit.
$background_attrs[ 'backgroundPositionXUnit' . $suffix ] = array(
'type' => 'string',
);
// background-position-number.
$background_attrs[ 'backgroundPositionXVal' . $suffix ] = array(
'type' => 'number',
);
// background-position-unit.
$background_attrs[ 'backgroundPositionYUnit' . $suffix ] = array(
'type' => 'string',
);
// background-position-number.
$background_attrs[ 'backgroundPositionYVal' . $suffix ] = array(
'type' => 'number',
);
// background-attachment.
$background_attrs[ 'backgroundAttachment' . $suffix ] = array(
'type' => 'string',
);
// background-repeat.
$background_attrs[ 'backgroundRepeat' . $suffix ] = array(
'type' => 'string',
);
// background-size.
$background_attrs[ 'backgroundSize' . $suffix ] = array(
'type' => 'string',
);
// background-size-unit.
$background_attrs[ 'backgroundSizeUnit' . $suffix ] = array(
'type' => 'string',
);
// background-size-number.
$background_attrs[ 'backgroundSizeVal' . $suffix ] = array(
'type' => 'number',
);
}
$attributes = array_merge(
$attributes,
$background_attrs
);
break;
case 'canvasSpacings':
$all_spacings = array(
'marginTop' => 'margin-top',
'marginBottom' => 'margin-bottom',
'marginLeft' => 'margin-left',
'marginRight' => 'margin-right',
'paddingTop' => 'padding-top',
'paddingBottom' => 'padding-bottom',
'paddingLeft' => 'padding-left',
'paddingRight' => 'padding-right',
);
$spacing_attrs = array();
foreach ( $breakpoints as $name => $breakpoint ) {
$suffix = '';
if ( $name && 'desktop' !== $name ) {
$suffix = '_' . $name;
}
// spacings.
foreach ( $all_spacings as $spacing_name => $spacing_val ) {
$spacing_attrs[ $spacing_name . $suffix ] = array(
'type' => 'number',
);
}
// link.
$spacing_attrs[ 'marginLink' . $suffix ] = array(
'type' => 'boolean',
);
$spacing_attrs[ 'paddingLink' . $suffix ] = array(
'type' => 'boolean',
);
// unit.
$spacing_attrs[ 'marginUnit' . $suffix ] = array(
'type' => 'string',
);
$spacing_attrs[ 'paddingUnit' . $suffix ] = array(
'type' => 'string',
);
}
$attributes = array_merge(
$attributes,
$spacing_attrs
);
break;
case 'canvasBorder':
$all_borders = array(
'borderWidthTop' => 'border-top-width',
'borderWidthBottom' => 'border-bottom-width',
'borderWidthLeft' => 'border-left-width',
'borderWidthRight' => 'border-right-width',
);
$all_borders_radius = array(
'borderRadiusTopLeft' => 'border-top-left-radius',
'borderRadiusTopRight' => 'border-top-right-radius',
'borderRadiusBottomLeft' => 'border-bottom-left-radius',
'borderRadiusBottomRight' => 'border-bottom-right-radius',
);
$border_attrs = array();
foreach ( $schemes as $name => $scheme ) {
$suffix = '';
if ( $name && 'default' !== $name ) {
$suffix = '_' . $name;
}
// border color.
$border_attrs[ 'borderColor' . $suffix ] = array(
'type' => 'string',
);
}
foreach ( $breakpoints as $name => $breakpoint ) {
$suffix = '';
if ( $name && 'desktop' !== $name ) {
$suffix = '_' . $name;
}
// borders.
foreach ( $all_borders as $border_name => $border_val ) {
$border_attrs[ $border_name . $suffix ] = array(
'type' => 'number',
);
}
// link.
$border_attrs[ 'borderWidthLink' . $suffix ] = array(
'type' => 'boolean',
);
// unit.
$border_attrs[ 'borderWidthUnit' . $suffix ] = array(
'type' => 'string',
);
// borders radius.
foreach ( $all_borders_radius as $border_name => $border_val ) {
$border_attrs[ $border_name . $suffix ] = array(
'type' => 'number',
);
}
// link.
$border_attrs[ 'borderRadiusLink' . $suffix ] = array(
'type' => 'boolean',
);
// unit.
$border_attrs[ 'borderRadiusUnit' . $suffix ] = array(
'type' => 'string',
);
}
$attributes = array_merge(
$attributes,
array(
'borderStyle' => array(
'type' => 'string',
),
),
$border_attrs
);
break;
case 'canvasResponsive':
foreach ( $breakpoints as $name => $breakpoint ) {
$attributes[ 'canvasResponsiveHide_' . $name ] = array(
'type' => 'boolean',
);
}
break;
case 'canvasUniqueClass':
$attributes['canvasClassName'] = array(
'type' => 'string',
);
break;
}
}
}
}
// Layouts.
if ( isset( $block['layouts'] ) && ! empty( $block['layouts'] ) ) {
$attributes['layout'] = array(
'type' => 'string',
'default' => '',
);
// set default value if only 1 layout present.
if ( 1 === count( $block['layouts'] ) ) {
$attributes['layout']['default'] = array_keys( $block['layouts'] )[0];
}
}
// Convert fields to attributes.
if ( isset( $block['fields'] ) ) {
$attributes = array_merge(
$attributes,
apply_filters( 'canvas_block_convert_fields_to_attributes', $block['fields'] )
);
}
$block_data = array(
'attributes' => $attributes,
'editor_script' => 'canvas-custom-blocks-editor-script',
'render_callback' => array( $this, 'render_custom_block' ),
);
// assets.
if ( isset( $block['style'] ) && $block['style'] ) {
$block_data['style'] = $block['style'];
}
if ( isset( $block['script'] ) && $block['script'] ) {
$block_data['script'] = $block['script'];
}
if ( isset( $block['editor_style'] ) && $block['editor_style'] ) {
$block_data['editor_style'] = $block['editor_style'];
}
// Editor script added as dependency in `canvas-custom-blocks-editor-script`.
register_block_type( $block['name'], $block_data );
}
}
/**
* Convert custom block fields to gutenberg block attributes.
*
* @param array $fields block fields.
* @return array attributes.
*/
public function convert_fields_to_attributes( $fields ) {
$schemes = cnvs_gutenberg()->get_schemes_data();
$breakpoints = cnvs_gutenberg()->get_breakpoints_data();
$all_breakpoints = cnvs_gutenberg()->get_all_breakpoints_data();
$attributes = array();
foreach ( $fields as $field ) {
$field_data = array(
'type' => 'string',
);
// defaults.
if ( isset( $field['default'] ) ) {
$field_data['default'] = $field['default'];
}
// custom type.
if ( isset( $field['type'] ) ) {
switch ( $field['type'] ) {
case 'toggle':
case 'type-boolean':
$field_data['type'] = 'boolean';
break;
case 'number':
case 'type-number':
$field_data['type'] = 'number';
break;
case 'image':
$field_data['type'] = 'object';
$field_data['default'] = array(
'url' => '',
'id' => 0,
);
break;
case 'gallery':
$field_data['type'] = 'array';
if ( isset( $field['items'] ) ) {
$field_data['items'] = $field['items'];
}
break;
case 'select':
case 'react-select':
if ( ! isset( $field['multiple'] ) || ! $field['multiple'] ) {
break;
}
case 'type-array':
$field_data['type'] = 'array';
if ( isset( $field['items'] ) ) {
$field_data['items'] = $field['items'];
}
break;
case 'toggle-list':
case 'query':
$field_data['type'] = 'object';
break;
}
}
// General attributes.
if ( isset( $field['key'] ) ) {
$attributes[ $field['key'] ] = $field_data;
// Schemes attributes.
if ( $schemes && ( 'color' === $field['type'] ) ) {
foreach ( $schemes as $name => $scheme ) {
if ( $name && 'default' !== $name ) {
$scheme_field_data = $field_data;
if ( isset( $field[ 'default_' . $name ] ) ) {
$scheme_field_data['default'] = $field[ 'default_' . $name ];
} elseif ( isset( $repsonsive_field_data['default'] ) ) {
unset( $scheme_field_data['default'] );
}
$attributes[ $field['key'] . '_' . $name ] = $scheme_field_data;
}
}
}
// Responsive attributes.
if ( isset( $field['responsive'] ) && $field['responsive'] ) {
foreach ( $all_breakpoints as $name => $breakpoint ) {
if ( $name && 'desktop' !== $name ) {
$repsonsive_field_data = $field_data;
if ( isset( $field['default_' . $name] ) ) {
$repsonsive_field_data['default'] = $field['default_' . $name];
} else if ( isset( $repsonsive_field_data['default'] ) ) {
unset( $repsonsive_field_data['default'] );
}
$attributes[ $field['key'] . '_' . $name ] = $repsonsive_field_data;
}
}
}
}
}
return $attributes;
}
/**
* Prepare server render attributes.
* For example, we need to remove field attributes, that hidden because of `active_callback`.
*
* @param array $attributes Values of attributes.
* @param array $data Fields data.
* @return array
*/
public function prepare_server_render_attributes( $attributes, $data ) {
if ( isset( $data['fields'] ) ) {
// remove attributes depending on `active_callback`.
foreach ( $data['fields'] as $field ) {
if (
isset( $field['active_callback'] ) &&
isset( $attributes[ $field['key'] ] ) &&
! CNVS_Gutenberg_Utils_Is_Field_Visible::check( $field, $attributes, $data['fields'], false )
) {
unset( $attributes[ $field['key'] ] );
}
}
}
return $attributes;
}
/**
* Get block template file path
*
* @param array $block Block data.
* @param array $attributes Block attributes.
*/
public function get_block_template( $block, $attributes ) {
$result = '';
// find block template.
if ( isset( $block['template'] ) && file_exists( $block['template'] ) ) {
$result = $block['template'];
}
// find current layout template.
if ( ! empty( $attributes['layout'] ) && isset( $block['layouts'][ $attributes['layout'] ] ) ) {
$layout = $block['layouts'][ $attributes['layout'] ];
if (
isset( $layout['template'] ) &&
file_exists( $layout['template'] )
) {
$result = $layout['template'];
// Include fallback.
} elseif ( isset( $layout['fallback'] ) ) {
$find_fallback = $this->get_block_template(
$block, array_merge(
$attributes,
array(
'layout' => isset( $layout['fallback']['layout'] ) ? $layout['fallback']['layout'] : 'undefined',
),
isset( $layout['fallback']['fields'] ) ? $layout['fallback']['fields'] : array()
)
);
if ( $find_fallback ) {
$result = $find_fallback;
}
}
}
return $result;
}
/**
* Include file and pass variables array.
*
* @param string $template file path.
* @param array $template_variables array of variables.
*/
public function include_file( $template, $template_variables ) {
if ( file_exists( $template ) ) {
extract( $template_variables );
include $template;
}
}
/**
* Render custom block callback.
*
* @param array $attributes block attributes.
* @param array $content block inner content.
* @return string block output.
*/
public function render_custom_block( $attributes, $content ) {
if ( ! isset( $attributes['canvasBlockName'] ) ) {
return '';
}
$block_name = $attributes['canvasBlockName'];
$block = $this->get_custom_block( $block_name );
if ( empty( $block ) ) {
return '';
}
$template = $this->get_block_template( $block, $attributes );
if ( ! $template || ! file_exists( $template ) ) {
return esc_html__( 'Block template is not available :(', 'canvas' );
}
// Generated HTML classes for blocks follow the `cnvs-block-{name}` nomenclature.
// Blocks provided by Canvas drop the prefixes 'canvas/'.
$block_class_name_prefix = $block_name;
$block_class_name_prefix = preg_replace( '/\//', '-', $block_class_name_prefix );
$block_class_name_prefix = preg_replace( '/^canvas-/', '', $block_class_name_prefix );
$block_class_name_prefix = 'cnvs-block-' . $block_class_name_prefix;
$class_name = $block_class_name_prefix;
// add classnames.
if ( isset( $attributes['canvasClassName'] ) && $attributes['canvasClassName'] ) {
$class_name .= ' ' . $attributes['canvasClassName'];
}
if ( isset( $attributes['layout'] ) ) {
$class_name .= ' ' . $block_class_name_prefix . '-layout-' . $attributes['layout'];
}
if ( isset( $attributes['className'] ) && $attributes['className'] ) {
$class_name .= ' ' . $attributes['className'];
}
if ( isset( $attributes['align'] ) && $attributes['align'] ) {
$class_name .= ' align' . $attributes['align'];
}
$attributes['className'] = trim( $class_name );
// Filter to prepare some attributes.
$attributes = apply_filters( 'canvas_block_prepare_server_render_attributes', $attributes, $block );
// Current layout fields options.
$options = array();
if ( isset( $attributes['layout'] ) ) {
foreach ( $attributes as $name => $val ) {
$layout_prefix = 'layout_' . $attributes['layout'] . '_';
if ( strpos( $name, $layout_prefix ) === 0 ) {
$options[ str_replace( $layout_prefix, '', $name ) ] = $val;
}
}
}
ob_start();
$template_variables = apply_filters(
'canvas_block_template_variables_' . $block_name, array(
'attributes' => $attributes,
'options' => $options,
'content' => $content,
), $block
);
$this->include_file( $template, $template_variables );
do_action( 'canvas_block_server_rendered_template_' . $block_name, $attributes );
$output = ob_get_clean();
return apply_filters( 'canvas_block_server_rendered_template_output', $output, $attributes, $options );
}
/**
* Add fields styles.
*
* @param string $block Block data.
* @param string $fields All available block fields.
* @return string
*/
public function get_custom_block_fields_output_styles( $block, $fields ) {
$return = '';
if ( isset( $block['attrs']['canvasClassName'] ) ) {
$canvas_classname = '.' . $block['attrs']['canvasClassName'];
$return = CNVS_Gutenberg_Fields_CSS_Output::get(
$canvas_classname,
$fields,
$block['attrs']
);
}
return $return;
}
/**
* Output custom styles for all `output` fields of custom blocks.
*
* @param string $styles The styles.
* @param array $blocks The blocks.
*/
public function blocks_dynamic_css( $styles = '', $blocks = array() ) {
// parse custom blocks and add fields output styles.
$custom_blocks = $this->get_custom_blocks();
if ( ! empty( $custom_blocks ) && ! empty( $blocks ) ) {
foreach ( $blocks as $block ) {
foreach ( $custom_blocks as $custom_block ) {
if ( $custom_block['name'] === $block['blockName'] ) {
$styles .= $this->get_custom_block_fields_output_styles(
$block,
isset( $custom_block['fields'] ) ? $custom_block['fields'] : array()
);
break;
}
}
}
}
return $styles;
}
}
new CNVS_Gutenberg_Blocks_Registration();
}
@@ -0,0 +1,691 @@
/**
* Styles
*/
import './style.scss';
/**
* Internal dependencies
*/
import isCoreBlockWithExt from '../../utils/is-core-block-with-ext';
import dynamicStylesBYkey from '../../utils/dynamic-styles-by-key';
import ComponentResponsiveWrapper from '../../components/responsive-wrapper';
const {
canvasBreakpoints,
} = window;
/**
* WordPress dependencies
*/
const { __ } = wp.i18n;
const {
addFilter,
} = wp.hooks;
const {
Component,
Fragment,
} = wp.element;
const {
createHigherOrderComponent,
} = wp.compose;
const {
BaseControl,
PanelBody,
RangeControl,
SelectControl,
DropZone,
Button,
} = wp.components;
const {
hasBlockSupport,
} = wp.blocks;
const {
InspectorControls,
MediaPlaceholder,
MediaUpload,
mediaUpload,
} = wp.blockEditor;
/**
* Extend block attributes with background image.
*
* @param {Object} blockSettings Original block settings.
* @param {String} name Original block name.
*
* @return {Object} Filtered block settings.
*/
function addAttribute( blockSettings, name ) {
let supports = hasBlockSupport( name, 'canvasBackgroundImage', false );
if ( isCoreBlockWithExt( name ) ) {
blockSettings.supports = {
...blockSettings.supports,
canvasBackgroundImage: ['core/group'].includes(name) ? true : false,
};
supports = ['core/group'].includes(name) ? true : false;
}
if ( supports ) {
if ( ! blockSettings.attributes ) {
blockSettings.attributes = {};
}
// Responsive attributes.
Object.keys( canvasBreakpoints ).forEach( ( breakpoint ) => {
let suffix = '';
if ( breakpoint && 'desktop' !== breakpoint ) {
suffix = `_${ breakpoint }`;
}
// Background Image.
if ( ! blockSettings.attributes[ `backgroundImage${ suffix }` ] ) {
blockSettings.attributes[ `backgroundImage${ suffix }` ] = {
type: 'object',
};
}
// Background Position.
if ( ! blockSettings.attributes[ `backgroundPosition${ suffix }` ] ) {
blockSettings.attributes[ `backgroundPosition${ suffix }` ] = {
type: 'string',
};
}
// Background Position X Unit.
if ( ! blockSettings.attributes[ `backgroundPositionXUnit${ suffix }` ] ) {
blockSettings.attributes[ `backgroundPositionXUnit${ suffix }` ] = {
type: 'string',
};
}
// Background Position X Val.
if ( ! blockSettings.attributes[ `backgroundPositionXVal${ suffix }` ] ) {
blockSettings.attributes[ `backgroundPositionXVal${ suffix }` ] = {
type: 'number',
};
}
// Background Position Y Unit.
if ( ! blockSettings.attributes[ `backgroundPositionYUnit${ suffix }` ] ) {
blockSettings.attributes[ `backgroundPositionYUnit${ suffix }` ] = {
type: 'string',
};
}
// Background Position Y Val.
if ( ! blockSettings.attributes[ `backgroundPositionYVal${ suffix }` ] ) {
blockSettings.attributes[ `backgroundPositionYVal${ suffix }` ] = {
type: 'number',
};
}
// Background Attachment.
if ( ! blockSettings.attributes[ `backgroundAttachment${ suffix }` ] ) {
blockSettings.attributes[ `backgroundAttachment${ suffix }` ] = {
type: 'string',
};
}
// Background Repeat.
if ( ! blockSettings.attributes[ `backgroundRepeat${ suffix }` ] ) {
blockSettings.attributes[ `backgroundRepeat${ suffix }` ] = {
type: 'string',
};
}
// Background Size Unit.
if ( ! blockSettings.attributes[ `backgroundSizeUnit${ suffix }` ] ) {
blockSettings.attributes[ `backgroundSizeUnit${ suffix }` ] = {
type: 'string',
};
}
// Background Size Val.
if ( ! blockSettings.attributes[ `backgroundSizeVal${ suffix }` ] ) {
blockSettings.attributes[ `backgroundSizeVal${ suffix }` ] = {
type: 'number',
};
}
} );
}
return blockSettings;
}
/**
* Override the default edit UI to include a new block inspector control for
* assigning the custom background image if needed.
*
* @param {function|Component} BlockEdit Original component.
*
* @return {string} Wrapped component.
*/
const withInspectorControl = createHigherOrderComponent( ( OriginalComponent ) => {
return class CanvasBackgroundImageWrapper extends Component {
constructor() {
super( ...arguments );
this.getCurrentBackgroundImageStyles = this.getCurrentBackgroundImageStyles.bind( this );
this.getCurrentValue = this.getCurrentValue.bind( this );
}
/**
* Get current background image for preview purposes.
*
* @returns {String} background image.
*/
getCurrentBackgroundImageStyles() {
const {
attributes,
} = this.props;
const {
canvasClassName,
} = attributes;
let customStyles = '';
if ( canvasClassName ) {
Object.keys( canvasBreakpoints ).forEach( ( name ) => {
let suffix = '';
let backgroundImageStyles = '';
if ( name && name !== 'desktop' ) {
suffix = '_' + name;
}
if ( typeof attributes[ 'backgroundImage' + suffix ] !== 'undefined' && attributes[ 'backgroundImage' + suffix ].id ) {
backgroundImageStyles += `background-image: url("${ attributes[ 'backgroundImage' + suffix ].url }"); `;
}
if ( typeof attributes[ 'backgroundImage'] !== 'undefined' && attributes[ 'backgroundImage'].id ) {
if ( typeof attributes[ 'backgroundPosition' + suffix ] !== 'undefined' ) {
if ( 'custom' === attributes[ 'backgroundPosition' + suffix ] ) {
let unitX = typeof attributes[ 'backgroundPositionXUnit' + suffix ] !== 'undefined' ? attributes[ 'backgroundPositionXUnit' + suffix ] : 'px';
let valX = typeof attributes[ 'backgroundPositionXVal' + suffix ] !== 'undefined' ? attributes[ 'backgroundPositionXVal' + suffix ] : '0';
let unitY = typeof attributes[ 'backgroundPositionYUnit' + suffix ] !== 'undefined' ? attributes[ 'backgroundPositionYUnit' + suffix ] : 'px';
let valY = typeof attributes[ 'backgroundPositionYVal' + suffix ] !== 'undefined' ? attributes[ 'backgroundPositionYVal' + suffix ] : '0';
backgroundImageStyles += `background-position: ${ valX }${ unitX } ${ valY }${ unitY }; `;
} else {
backgroundImageStyles += `background-position: ${ attributes[ 'backgroundPosition' + suffix ] }; `;
}
}
if ( typeof attributes[ 'backgroundAttachment' + suffix ] !== 'undefined' ) {
backgroundImageStyles += `background-attachment: ${ attributes[ 'backgroundAttachment' + suffix ] }; `;
}
if ( typeof attributes[ 'backgroundRepeat' + suffix ] !== 'undefined' ) {
backgroundImageStyles += `background-repeat: ${ attributes[ 'backgroundRepeat' + suffix ] }; `;
}
if ( typeof attributes[ 'backgroundSize' + suffix ] !== 'undefined' ) {
backgroundImageStyles += `background-size: ${ attributes[ 'backgroundSize' + suffix ] }; `;
if ( 'custom' === attributes[ 'backgroundSize' + suffix ] ) {
if ( typeof attributes[ 'backgroundSizeVal' + suffix ] !== 'undefined' ) {
let unit = typeof attributes[ 'backgroundSizeUnit' + suffix ] !== 'undefined' ? attributes[ 'backgroundSizeUnit' + suffix ] : '%';
backgroundImageStyles += `background-size: ${ attributes[ 'backgroundSizeVal' + suffix ] }${ unit } auto; `;
}
} else {
backgroundImageStyles += `background-size: ${ attributes[ 'backgroundSize' + suffix ] }; `;
}
}
}
if ( backgroundImageStyles ) {
backgroundImageStyles = `.${ canvasClassName } { ${ backgroundImageStyles } }`;
if ( suffix ) {
backgroundImageStyles = `@media (max-width: ${ canvasBreakpoints[ name ].width }px) { ${ backgroundImageStyles } } `;
}
customStyles += backgroundImageStyles;
}
} );
}
return customStyles;
}
/**
* Get current value.
*
* @param {String} name - name.
* @param {String} suffix - suffix.
*
* @return {Int}
*/
getCurrentValue( name, suffix = '' ) {
const {
attributes,
} = this.props;
return attributes[ `${ name }${ suffix }` ];
}
render() {
if ( ! hasBlockSupport( this.props.name, 'canvasBackgroundImage', false ) ) {
return <OriginalComponent { ...this.props } />;
}
const {
attributes,
setAttributes,
} = this.props;
const {
canvasClassName,
} = attributes;
dynamicStylesBYkey( 'background-image', canvasClassName, this.getCurrentBackgroundImageStyles() );
return (
<Fragment>
<OriginalComponent
{ ...this.props }
{ ...this.state }
setState={ this.setState }
/>
<InspectorControls>
<PanelBody
title={ __( 'Background Image' ) }
initialOpen={ false }
>
<ComponentResponsiveWrapper>
{ ( { responsiveSuffix, ComponentResponsiveDropdown } ) => {
var controlName = 'backgroundImage' + responsiveSuffix;
// Get values of control.
var id = controlName in attributes ? attributes[controlName].id : 0;
var url = controlName in attributes ? attributes[controlName].url : '';
return (
<Fragment>
<h2>
{ __( 'Image' ) }
<ComponentResponsiveDropdown />
</h2>
<Fragment>
<BaseControl
>
{ ! id ? (
<MediaPlaceholder
icon="format-image"
labels={ {
title: __( 'Image' ),
name: __( 'image' ),
} }
onSelect={ ( image ) => {
setAttributes( { [ controlName ] : {
id: image.id,
url: image.url,
} } );
} }
accept="image/*"
allowedTypes={ [ 'image' ] }
disableMaxUploadErrorMessages
onError={ ( e ) => {
console.log( e );
} }
/>
) : '' }
{ url ? (
<Fragment>
<DropZone
onFilesDrop={ ( files ) => {
mediaUpload( {
allowedTypes: [ 'image' ],
filesList: files,
onFileChange: ( image ) => {
setAttributes( { [ controlName ]: {
id: image.id,
url: image.url,
} } );
},
onError( e ) {
console.log( e );
},
} );
} }
/>
{ url ? (
<img src={ url } />
) : '' }
<div>
<Button
isDefault={ true }
onClick={ () => {
setAttributes( { [ controlName ]: {
id: 0,
url: '',
} } );
} }
>
{ __( 'Remove Image' ) }
</Button>
</div>
</Fragment>
) : '' }
</BaseControl>
</Fragment>
</Fragment>
);
} }
</ComponentResponsiveWrapper>
{/* Additional Controls */}
{ 'backgroundImage' in attributes && typeof attributes['backgroundImage'] === 'object' && attributes['backgroundImage'].id ? (
<div>
<ComponentResponsiveWrapper>
{ ( { responsiveSuffix, ComponentResponsiveDropdown } ) => {
var controlName = 'backgroundPosition' + responsiveSuffix;
return (
<Fragment>
<h2>
{ __( 'Position' ) }
<ComponentResponsiveDropdown />
</h2>
<SelectControl
label={ false }
help={ false }
multiple={ false }
value={ controlName in attributes ? attributes[controlName] : '' }
options={ [
{ label: 'Default', value: '' },
{ label: 'Center Center', value: 'center center' },
{ label: 'Center Left', value: 'center left' },
{ label: 'Center Right', value: 'center right' },
{ label: 'Top Center', value: 'top center' },
{ label: 'Top Left', value: 'center left' },
{ label: 'Top Right', value: 'top right' },
{ label: 'Bottom Center', value: 'bottom center' },
{ label: 'Bottom Left', value: 'bottom left' },
{ label: 'Bottom Left', value: 'bottom right' },
{ label: 'Custom', value: 'custom' },
] }
onChange={ ( val ) => {
setAttributes( { [ controlName ]: val });
} }
/>
</Fragment>
);
} }
</ComponentResponsiveWrapper>
<ComponentResponsiveWrapper>
{ ( { responsiveSuffix, ComponentResponsiveDropdown } ) => {
var controlName = 'backgroundPosition' + responsiveSuffix;
var unitControlName = 'backgroundPositionXUnit' + responsiveSuffix;
var valControlName = 'backgroundPositionXVal' + responsiveSuffix;
var units = [ 'px', 'em', '%' ];
if ( 'custom' === attributes[controlName] ) {
return (
<Fragment>
<h2 class="cnvs-component-background-image-units">
{ __( 'X Position' ) }
<ComponentResponsiveDropdown />
<div className="cnvs-component-background-image-units-controls">
{ units.map( ( unit ) => {
var unitVal = unitControlName in attributes && attributes[unitControlName] ? attributes[unitControlName] : 'px';
return (
<Button
isPrimary={ ( unitVal === unit ) ? true : false }
onClick={ () => {
setAttributes( { [ unitControlName ]: unit });
} }
>
{ unit }
</Button>
);
} ) }
</div>
</h2>
<RangeControl
label={ false }
help={ false }
min={ -2000 }
max={ 2000 }
step={ 1 }
value={ valControlName in attributes ? attributes[valControlName] : 0 }
onChange={ ( val ) => {
setAttributes( { [ valControlName ]: val });
} }
/>
</Fragment>
);
}
} }
</ComponentResponsiveWrapper>
<ComponentResponsiveWrapper>
{ ( { responsiveSuffix, ComponentResponsiveDropdown } ) => {
var controlName = 'backgroundPosition' + responsiveSuffix;
var unitControlName = 'backgroundPositionYUnit' + responsiveSuffix;
var valControlName = 'backgroundPositionYVal' + responsiveSuffix;
var units = [ 'px', 'em', '%' ];
if ( 'custom' === attributes[controlName] ) {
return (
<Fragment>
<h2 class="cnvs-component-background-image-units">
{ __( 'Y Position' ) }
<ComponentResponsiveDropdown />
<div className="cnvs-component-background-image-units-controls">
{ units.map( ( unit ) => {
var unitVal = unitControlName in attributes && attributes[unitControlName] ? attributes[unitControlName] : 'px';
return (
<Button
isPrimary={ ( unitVal === unit ) ? true : false }
onClick={ () => {
setAttributes( { [ unitControlName ]: unit });
} }
>
{ unit }
</Button>
);
} ) }
</div>
</h2>
<RangeControl
label={ false }
help={ false }
min={ -2000 }
max={ 2000 }
step={ 1 }
value={ valControlName in attributes ? attributes[valControlName] : 0 }
onChange={ ( val ) => {
setAttributes( { [ valControlName ]: val });
} }
/>
</Fragment>
);
}
} }
</ComponentResponsiveWrapper>
<ComponentResponsiveWrapper>
{ ( { responsiveSuffix, ComponentResponsiveDropdown } ) => {
var controlName = 'backgroundAttachment' + responsiveSuffix;
return (
<Fragment>
<h2>
{ __( 'Attachment' ) }
<ComponentResponsiveDropdown />
</h2>
<SelectControl
label={ false }
help={ false }
multiple={ false }
value={ controlName in attributes ? attributes[controlName] : '' }
options={ [
{ label: 'Default', value: '' },
{ label: 'Scroll', value: 'scroll' },
{ label: 'Fixed', value: 'fixed' },
] }
onChange={ ( val ) => {
setAttributes( { [ controlName ]: val });
} }
/>
</Fragment>
);
} }
</ComponentResponsiveWrapper>
<ComponentResponsiveWrapper>
{ ( { responsiveSuffix, ComponentResponsiveDropdown } ) => {
var controlName = 'backgroundRepeat' + responsiveSuffix;
return (
<Fragment>
<h2>
{ __( 'Repeat' ) }
<ComponentResponsiveDropdown />
</h2>
<SelectControl
label={ false }
help={ false }
multiple={ false }
value={ controlName in attributes ? attributes[controlName] : '' }
options={ [
{ label: 'Default', value: '' },
{ label: 'No-repeat', value: 'no-repeat' },
{ label: 'Repeat', value: 'repeat' },
{ label: 'Repeat-x', value: 'repeat-x' },
{ label: 'Repeat-y', value: 'repeat-y' },
] }
onChange={ ( val ) => {
setAttributes( { [ controlName ]: val });
} }
/>
</Fragment>
);
} }
</ComponentResponsiveWrapper>
<ComponentResponsiveWrapper>
{ ( { responsiveSuffix, ComponentResponsiveDropdown } ) => {
var controlName = 'backgroundSize' + responsiveSuffix;
return (
<Fragment>
<h2>
{ __( 'Size' ) }
<ComponentResponsiveDropdown />
</h2>
<SelectControl
label={ false }
help={ false }
multiple={ false }
value={ controlName in attributes ? attributes[controlName] : '' }
options={ [
{ label: 'Default', value: '' },
{ label: 'Auto', value: 'auto' },
{ label: 'Cover', value: 'cover' },
{ label: 'Contain', value: 'contain' },
{ label: 'Custom', value: 'custom' },
] }
onChange={ ( val ) => {
setAttributes( { [ controlName ]: val });
} }
/>
</Fragment>
);
} }
</ComponentResponsiveWrapper>
<ComponentResponsiveWrapper>
{ ( { responsiveSuffix, ComponentResponsiveDropdown } ) => {
var controlName = 'backgroundSize' + responsiveSuffix;
var unitControlName = 'backgroundSizeUnit' + responsiveSuffix;
var valControlName = 'backgroundSizeVal' + responsiveSuffix;
var units = [ 'px', 'em', '%' ];
if ( 'custom' === attributes[controlName] ) {
return (
<Fragment>
<h2 class="cnvs-component-background-image-units">
{ __( 'Width' ) }
<ComponentResponsiveDropdown />
<div className="cnvs-component-background-image-units-controls">
{ units.map( ( unit ) => {
var unitVal = unitControlName in attributes && attributes[unitControlName] ? attributes[unitControlName] : '%';
return (
<Button
isPrimary={ ( unitVal === unit ) ? true : false }
onClick={ () => {
setAttributes( { [ unitControlName ]: unit });
} }
>
{ unit }
</Button>
);
} ) }
</div>
</h2>
<RangeControl
label={ false }
help={ false }
min={ 0 }
max={ 2000 }
step={ 1 }
value={ valControlName in attributes ? attributes[valControlName] : '' }
onChange={ ( val ) => {
setAttributes( { [ valControlName ]: val });
} }
/>
</Fragment>
);
}
} }
</ComponentResponsiveWrapper>
</div>
) : '' }
</PanelBody>
</InspectorControls>
</Fragment>
);
}
};
}, 'withInspectorControl' );
// Init filters.
addFilter( 'blocks.registerBlockType', 'canvas/background-image/additional-attributes', addAttribute );
addFilter( 'editor.BlockEdit', 'canvas/background-image/additional-attributes', withInspectorControl );
@@ -0,0 +1,450 @@
/**
* Internal dependencies
*/
import isCoreBlockWithExt from '../../utils/is-core-block-with-ext';
import dynamicStylesBYkey from '../../utils/dynamic-styles-by-key';
import ComponentColors from '../../components/colors';
import ComponentSpacings from '../../components/spacings';
import ComponentRadius from '../../components/radius';
import ComponentSchemeWrapper from '../../components/scheme-wrapper';
import ComponentResponsiveWrapper from '../../components/responsive-wrapper';
const {
canvasSchemes,
canvasBreakpoints,
} = window;
/**
* WordPress dependencies
*/
const { __ } = wp.i18n;
const {
addFilter,
} = wp.hooks;
const {
Component,
Fragment,
} = wp.element;
const {
createHigherOrderComponent,
} = wp.compose;
const { InspectorControls } = wp.blockEditor;
const {
PanelBody,
SelectControl,
} = wp.components;
const {
hasBlockSupport,
} = wp.blocks;
const allBorders = {
'borderWidthTop': 'border-top-width',
'borderWidthBottom': 'border-bottom-width',
'borderWidthLeft': 'border-left-width',
'borderWidthRight': 'border-right-width',
};
const allBordersRadius = {
'borderRadiusTopLeft': 'border-top-left-radius',
'borderRadiusTopRight': 'border-top-right-radius',
'borderRadiusBottomLeft': 'border-bottom-left-radius',
'borderRadiusBottomRight': 'border-bottom-right-radius',
};
/**
* Extend block attributes with borders.
*
* @param {Object} blockSettings Original block settings.
* @param {String} name Original block name.
*
* @return {Object} Filtered block settings.
*/
function addAttribute( blockSettings, name ) {
let supports = hasBlockSupport( name, 'canvasBorder', false );
// add support to core blocks
if ( isCoreBlockWithExt( name ) ) {
blockSettings.supports = {
...blockSettings.supports,
canvasBorder: true,
};
supports = true;
}
if ( supports ) {
if ( ! blockSettings.attributes ) {
blockSettings.attributes = {};
}
// style.
if ( ! blockSettings.attributes.borderStyle ) {
blockSettings.attributes.borderStyle = {
type: 'string',
};
}
// color.
Object.keys( canvasSchemes ).forEach( ( scheme ) => {
let suffix = '';
if ( scheme && 'default' !== scheme ) {
suffix = `_${ scheme }`;
}
if ( ! blockSettings.attributes[ `borderColor${ suffix }` ] ) {
blockSettings.attributes[ `borderColor${ suffix }` ] = {
type: 'string',
};
}
} );
// responsive attributes.
Object.keys( canvasBreakpoints ).forEach( ( breakpoint ) => {
let suffix = '';
if ( breakpoint && 'desktop' !== breakpoint ) {
suffix = `_${ breakpoint }`;
}
// width.
Object.keys( allBorders ).forEach( ( spacing ) => {
if ( ! blockSettings.attributes[ spacing + suffix ] ) {
blockSettings.attributes[ spacing + suffix ] = {
type: 'number',
};
}
} );
// link.
if ( ! blockSettings.attributes[ `borderWidthLink${ suffix }` ] ) {
blockSettings.attributes[ `borderWidthLink${ suffix }` ] = {
type: 'boolean',
};
}
// unit.
if ( ! blockSettings.attributes[ `borderWidthUnit${ suffix }` ] ) {
blockSettings.attributes[ `borderWidthUnit${ suffix }` ] = {
type: 'string',
};
}
// radius.
Object.keys( allBordersRadius ).forEach( ( radius ) => {
if ( ! blockSettings.attributes[ radius + suffix ] ) {
blockSettings.attributes[ radius + suffix ] = {
type: 'number',
};
}
} );
// link.
if ( ! blockSettings.attributes[ `borderRadiusLink${ suffix }` ] ) {
blockSettings.attributes[ `borderRadiusLink${ suffix }` ] = {
type: 'boolean',
};
}
// unit.
if ( ! blockSettings.attributes[ `borderRadiusUnit${ suffix }` ] ) {
blockSettings.attributes[ `borderRadiusUnit${ suffix }` ] = {
type: 'string',
};
}
} );
}
return blockSettings;
}
/**
* Override the default edit UI to include a new block inspector control for
* assigning the custom borders if needed.
*
* @param {function|Component} BlockEdit Original component.
*
* @return {string} Wrapped component.
*/
const withInspectorControl = createHigherOrderComponent( ( OriginalComponent ) => {
return class CanvasBordersWrapper extends Component {
constructor() {
super( ...arguments );
this.getCurrentBorderStyles = this.getCurrentBorderStyles.bind( this );
this.getCurrentValue = this.getCurrentValue.bind( this );
}
/**
* Get current border styles for preview purposes.
*
* @returns {String} border styles.
*/
getCurrentBorderStyles() {
const {
attributes,
} = this.props;
const {
canvasClassName,
borderStyle,
} = attributes;
let customStyles = '';
if ( canvasClassName ) {
Object.keys( canvasBreakpoints ).forEach( ( name ) => {
let suffix = '';
let borderRadiusUnit = 'px';
let breakpointStyles = '';
if ( name && name !== 'desktop' ) {
suffix = '_' + name;
}
if ( attributes[ 'borderRadiusUnit' + suffix ] ) {
borderRadiusUnit = attributes[ 'borderRadiusUnit' + suffix ];
}
Object.keys( allBordersRadius ).forEach( ( radius ) => {
if ( typeof attributes[ radius + suffix ] !== 'undefined' ) {
breakpointStyles += `${ allBordersRadius[ radius ] }: ${ attributes[ radius + suffix ] }${ borderRadiusUnit }; `;
}
} );
if ( breakpointStyles ) {
breakpointStyles = `.${ canvasClassName } { ${ breakpointStyles } }`;
if ( suffix ) {
breakpointStyles = `@media (max-width: ${ canvasBreakpoints[ name ].width }px) { ${ breakpointStyles } } `;
}
customStyles += breakpointStyles;
}
} );
}
if ( canvasClassName && borderStyle ) {
let mainStyles = '';
mainStyles = `border-style: ${ borderStyle };`;
mainStyles += `border-width: 0;`;
customStyles += `.${ canvasClassName } { ${ mainStyles } }`;
Object.keys( canvasSchemes ).forEach( ( name ) => {
let rule = '';
let suffix = '';
if ( name && 'default' !== name ) {
suffix = `_${ name }`;
rule = `[data-scheme="${ name }"]`;
}
if ( attributes[ 'borderColor' + suffix ] ) {
mainStyles = `border-color: ${ attributes[ 'borderColor' + suffix ] };`;
customStyles += `${rule} .${ canvasClassName } { ${ mainStyles } }`;
}
} );
Object.keys( canvasBreakpoints ).forEach( ( name ) => {
let suffix = '';
let borderWidthUnit = 'px';
let breakpointStyles = '';
if ( name && name !== 'desktop' ) {
suffix = '_' + name;
}
if ( attributes[ 'borderWidthUnit' + suffix ] ) {
borderWidthUnit = attributes[ 'borderWidthUnit' + suffix ];
}
Object.keys( allBorders ).forEach( ( spacing ) => {
if ( typeof attributes[ spacing + suffix ] !== 'undefined' ) {
breakpointStyles += `${ allBorders[ spacing ] }: ${ attributes[ spacing + suffix ] }${ borderWidthUnit }; `;
}
} );
if ( breakpointStyles ) {
breakpointStyles = `.${ canvasClassName } { ${ breakpointStyles } }`;
if ( suffix ) {
breakpointStyles = `@media (max-width: ${ canvasBreakpoints[ name ].width }px) { ${ breakpointStyles } } `;
}
customStyles += breakpointStyles;
}
} );
}
return customStyles;
}
/**
* Get current value.
*
* @param {String} name - name.
* @param {String} suffix - suffix.
*
* @return {Int}
*/
getCurrentValue( name, suffix = '' ) {
const {
attributes,
} = this.props;
return attributes[ `${ name }${ suffix }` ];
}
render() {
if ( ! hasBlockSupport( this.props.name, 'canvasBorder', false ) ) {
return <OriginalComponent { ...this.props } />;
}
const {
attributes,
setAttributes,
} = this.props;
const {
canvasClassName,
} = attributes;
dynamicStylesBYkey( 'borders', canvasClassName, this.getCurrentBorderStyles() );
// add new borders controls.
return (
<Fragment>
<OriginalComponent
{ ...this.props }
{ ...this.state }
setState={ this.setState }
/>
<InspectorControls>
<PanelBody
title={ __( 'Borders' ) }
initialOpen={ false }
>
<ComponentResponsiveWrapper>
{ ( { responsiveSuffix, ComponentResponsiveDropdown } ) => {
return (
<Fragment>
<h2>
{ __( 'Radius' ) }
<ComponentResponsiveDropdown />
</h2>
<ComponentRadius
prefix="borderRadius"
suffix={ responsiveSuffix }
units={ [ 'px', '%' ] }
topLeft={ this.getCurrentValue( 'borderRadiusTopLeft', responsiveSuffix ) }
topRight={ this.getCurrentValue( 'borderRadiusTopRight', responsiveSuffix ) }
bottomLeft={ this.getCurrentValue( 'borderRadiusBottomLeft', responsiveSuffix ) }
bottomRight={ this.getCurrentValue( 'borderRadiusBottomRight', responsiveSuffix ) }
link={ this.getCurrentValue( 'borderRadiusLink', responsiveSuffix ) }
unit={ this.getCurrentValue( 'borderRadiusUnit', responsiveSuffix ) }
onChange={ ( attributes ) => {
setAttributes( attributes );
} }
/>
</Fragment>
);
} }
</ComponentResponsiveWrapper>
<SelectControl
label={ __( 'Border' ) }
value={ attributes.borderStyle }
options={ [
{
label: __( 'None' ),
value: '',
}, {
label: __( 'Solid' ),
value: 'solid',
}, {
label: __( 'Dashed' ),
value: 'dashed',
}, {
label: __( 'Dotted' ),
value: 'dotted',
}, {
label: __( 'Double' ),
value: 'double',
},
] }
onChange={ ( val ) => {
setAttributes( {
borderStyle: val,
} );
} }
/>
{ attributes.borderStyle ? (
<ComponentResponsiveWrapper>
{ ( { responsiveSuffix, ComponentResponsiveDropdown } ) => {
return (
<Fragment>
<h2>
{ __( 'Width' ) }
<ComponentResponsiveDropdown />
</h2>
<ComponentSpacings
prefix="borderWidth"
suffix={ responsiveSuffix }
units={ [ 'px' ] }
top={ this.getCurrentValue( 'borderWidthTop', responsiveSuffix ) }
bottom={ this.getCurrentValue( 'borderWidthBottom', responsiveSuffix ) }
left={ this.getCurrentValue( 'borderWidthLeft', responsiveSuffix ) }
right={ this.getCurrentValue( 'borderWidthRight', responsiveSuffix ) }
link={ this.getCurrentValue( 'borderWidthLink', responsiveSuffix ) }
unit={ this.getCurrentValue( 'borderWidthUnit', responsiveSuffix ) }
onChange={ ( attributes ) => {
setAttributes( attributes );
} }
/>
</Fragment>
);
} }
</ComponentResponsiveWrapper>
) : '' }
{ attributes.borderStyle ? (
<ComponentSchemeWrapper>
{({ schemeSuffix, ComponentSchemeDropdown }) => {
return (
<Fragment>
<h2>
{ __( 'Color' ) }
<ComponentSchemeDropdown />
</h2>
<ComponentColors
slug='borderColor'
suffix={ schemeSuffix }
val={ this.getCurrentValue( 'borderColor', schemeSuffix ) }
onChange={ ( attributes ) => {
setAttributes( attributes );
} }
/>
</Fragment>
)
}}
</ComponentSchemeWrapper>
) : '' }
</PanelBody>
</InspectorControls>
</Fragment>
);
}
};
}, 'withInspectorControl' );
// Init filters.
addFilter( 'blocks.registerBlockType', 'canvas/borders/additional-attributes', addAttribute );
addFilter( 'editor.BlockEdit', 'canvas/borders/additional-attributes', withInspectorControl );
@@ -0,0 +1,358 @@
/**
* Internal dependencies
*/
import './style.scss';
import QueryControl from '../../components/query-control';
import getParentBlock from '../../../gutenberg/utils/get-parent-block';
import './posts-block';
/**
* WordPress dependencies
*/
const { __ } = wp.i18n;
const {
addFilter,
} = wp.hooks;
const {
Component,
Fragment,
} = wp.element;
const { InspectorControls } = wp.blockEditor;
const {
PanelBody,
ToggleControl,
} = wp.components;
const {
createHigherOrderComponent,
compose,
} = wp.compose;
const {
withSelect,
withDispatch,
} = wp.data;
/**
* Extend block attributes with posts block query attributes.
*
* @param {Object} blockSettings Original block settings.
* @param {String} name Original block name.
*
* @return {Object} Filtered block settings.
*/
function addAttribute( blockSettings, name ) {
// add support to core blocks
if ( 'core/group' === name ) {
if ( ! blockSettings.attributes ) {
blockSettings.attributes = {};
}
if ( ! blockSettings.attributes.canvasPostsQuery ) {
blockSettings.attributes.canvasPostsQuery = {
type: 'boolean',
};
}
}
return blockSettings;
}
/**
* Get all posts blocks inside Posts Group block.
*
* @param {Object} block - block data.
*
* @returns {Array} - list of collected blocks.
*/
function getAllPostsBlocks( block ) {
let result = [];
if ( ! block ) {
return result;
}
// Collect posts block data.
if ( block.name && 'canvas/posts' === block.name ) {
result.push( block );
}
// Check inner blocks.
if ( block.innerBlocks && block.innerBlocks.length ) {
block.innerBlocks.forEach( ( innerBlock ) => {
result = [
...result,
...getAllPostsBlocks( innerBlock ),
];
} );
}
return result;
}
/**
* Override the default edit UI to include a new block inspector control for
* assigning the Query settings to Group block.
*
* @param {function|Component} BlockEdit Original component.
*
* @return {string} Wrapped component.
*/
const withInspectorControl = createHigherOrderComponent( ( OriginalComponent ) => {
class CanvasGroupPostsQueryWrapper extends Component {
constructor() {
super( ...arguments );
this.update = this.update.bind( this );
this.getQueryAttrs = this.getQueryAttrs.bind( this );
this.updateQueryAttrs = this.updateQueryAttrs.bind( this );
this.maybeUpdatePostsBlockAttrs = this.maybeUpdatePostsBlockAttrs.bind( this );
}
componentDidMount() {
this.update();
}
componentDidUpdate() {
this.update();
}
update() {
const {
attributes,
canvasPostsBlocks,
allowCanvasPostsQuery,
} = this.props;
if ( ! allowCanvasPostsQuery || ! attributes.canvasPostsQuery ) {
return;
}
// Update inner Posts blocks attributes.
this.maybeUpdatePostsBlockAttrs( canvasPostsBlocks );
}
/**
* Get group query attrs.
* On first run get attrs from the first found Posts block.
*
* @returns {Object} - grouped posts attrs.
*/
getQueryAttrs() {
const {
canvasPostsBlocks,
} = this.props;
if ( ! this.canvasPostsGroupAttrs ) {
// default attributes.
this.canvasPostsGroupAttrs = {
avoidDuplicatePosts: false,
query: {
posts_type: 'post',
categories: '',
tags: '',
exclude_categories: '',
exclude_tags: '',
formats: '',
posts: '',
offset: '',
orderby: 'date',
order: 'DESC',
time_frame: '',
},
};
// attributes from the first inner Posts block.
if ( canvasPostsBlocks && canvasPostsBlocks.length ) {
if ( canvasPostsBlocks[ 0 ].attributes && canvasPostsBlocks[ 0 ].attributes.query ) {
this.canvasPostsGroupAttrs.query = {
...this.canvasPostsGroupAttrs.query,
...canvasPostsBlocks[ 0 ].attributes.query,
};
}
if ( canvasPostsBlocks[ 0 ].attributes && canvasPostsBlocks[ 0 ].attributes.avoidDuplicatePosts ) {
this.canvasPostsGroupAttrs.avoidDuplicatePosts = true;
}
}
}
return this.canvasPostsGroupAttrs;
}
/**
* Update query attrs.
*/
updateQueryAttrs( newAttrs ) {
const currentAttrs = this.getQueryAttrs();
this.canvasPostsGroupAttrs = {
...currentAttrs,
...newAttrs,
query: {
...currentAttrs.query,
...newAttrs.query || {},
},
};
this.update();
}
/**
* Update posts blocks Query attributes to work properly inside Posts Group block.
*
* @param {Array} blocks block list.
*/
maybeUpdatePostsBlockAttrs( blocks ) {
if ( this.groupBlockAttrsBusy ) {
return;
}
this.groupBlockAttrsBusy = true;
const {
updateBlockAttributes,
} = this.props;
const newAttrs = this.getQueryAttrs();
const queryString = JSON.stringify( newAttrs );
blocks.forEach( ( { clientId, attributes } ) => {
const postsQueryString = JSON.stringify( {
avoidDuplicatePosts: attributes.avoidDuplicatePosts,
query: attributes.query,
} );
if (
attributes.relatedPosts ||
postsQueryString !== queryString
) {
updateBlockAttributes( clientId, {
relatedPosts: false,
avoidDuplicatePosts: newAttrs.avoidDuplicatePosts,
query: {
...newAttrs.query,
},
} );
}
} );
setTimeout( () => {
this.groupBlockAttrsBusy = false;
}, 100 );
}
render() {
const {
setAttributes,
attributes,
allowCanvasPostsQuery,
} = this.props;
if ( ! attributes.canvasPostsQuery && ! allowCanvasPostsQuery ) {
return <OriginalComponent { ...this.props } />;
}
const postsGroupAttrs = attributes.canvasPostsQuery ? this.getQueryAttrs() : {};
// add new spacings controls.
return (
<Fragment>
<OriginalComponent { ...this.props } />
<InspectorControls>
<PanelBody
title={ __( 'Query Settings', 'canvas' ) }
initialOpen={ false }
>
<ToggleControl
label={ __( 'Enable global Query settings for child Posts blocks', 'canvas' ) }
checked={ attributes.canvasPostsQuery }
onChange={ () => {
setAttributes( {
canvasPostsQuery: ! attributes.canvasPostsQuery,
} );
} }
/>
{ attributes.canvasPostsQuery ? (
<Fragment>
<QueryControl
value={ postsGroupAttrs.query }
onChange={ ( val ) => {
this.updateQueryAttrs( {
query: val,
} );
} }
/>
<ToggleControl
label={ __( 'Avoid Duplicate Posts', 'canvas' ) }
help={ __( 'Changes will be visible on frontend only.', 'canvas' ) }
checked={ postsGroupAttrs.avoidDuplicatePosts }
onChange={ ( val ) => {
this.updateQueryAttrs( {
avoidDuplicatePosts: val,
} );
} }
/>
</Fragment>
) : '' }
</PanelBody>
</InspectorControls>
</Fragment>
);
}
}
return compose(
withSelect((select, ownProps) => {
if ( 'core/group' !== ownProps.name ) {
return {};
}
const {
getBlockHierarchyRootClientId,
getBlockParents,
getBlock,
} = select('core/block-editor');
const currentBlock = getBlock( ownProps.clientId );
const rootBlock = getBlock( getBlockHierarchyRootClientId( ownProps.clientId ) );
const parentBlocks = getBlockParents(ownProps.clientId);
const canvasPostsBlocks = getAllPostsBlocks( currentBlock );
let isInsideAnotherGroupQuery = false;
// Trying to find parent Posts Group block.
if ( rootBlock && (undefined === typeof rootBlock.attributes.ref || ! rootBlock.attributes.ref) && parentBlocks ) {
parentBlocks.forEach((parentID) => {
let parentBlock = getBlock( parentID );
if ( ! isInsideAnotherGroupQuery ) {
isInsideAnotherGroupQuery = 'core/group' === parentBlock.name && parentBlock.attributes.canvasPostsQuery ? parentBlock : false;
}
});
}
return {
allowCanvasPostsQuery: canvasPostsBlocks.length && ! isInsideAnotherGroupQuery,
canvasPostsBlocks,
};
}),
withDispatch((dispatch) => {
const {
updateBlockAttributes,
} = dispatch('core/block-editor');
return {
updateBlockAttributes,
};
})
)(CanvasGroupPostsQueryWrapper);
}, 'withInspectorControl' );
// Init filters.
addFilter( 'blocks.registerBlockType', 'canvas/group-block-posts-query/additional-attributes', addAttribute );
addFilter( 'editor.BlockEdit', 'canvas/group-block-posts-query/additional-attributes', withInspectorControl );
@@ -0,0 +1,191 @@
/**
* Internal dependencies
*/
import getParentBlock from '../../../gutenberg/utils/get-parent-block';
/**
* WordPress dependencies
*/
const { __ } = wp.i18n;
const {
Component,
Fragment,
} = wp.element;
const {
addFilter,
} = wp.hooks;
const {
Notice,
Button,
} = wp.components;
const {
createHigherOrderComponent,
compose,
} = wp.compose;
const {
withSelect,
} = wp.data;
/**
* Hide Query Settings if Group block override it.
*
* @param {JSX} sectionFields - section fields.
* @param {Object} props - block props.
*
* @returns {JSX} - section fields.
*/
function hidePostsQuery( sectionFields, { sectionName, props } ) {
const {
blockProps,
} = props;
if ( ! blockProps || ! blockProps.name || 'canvas/posts' !== blockProps.name || sectionName !== 'query' ) {
return sectionFields;
}
const {
getBlockHierarchyRootClientId,
getBlockParents,
getBlock,
} = wp.data.select('core/block-editor');
const {
selectBlock,
} = wp.data.dispatch('core/block-editor');
const rootBlock = getBlock( getBlockHierarchyRootClientId( blockProps.clientId ) );
const parentBlocks = getBlockParents(blockProps.clientId);
let groupQueryBlock = false;
// Trying to find parent Posts Group block.
if ( rootBlock && (undefined === typeof rootBlock.attributes.ref || ! rootBlock.attributes.ref) && parentBlocks ) {
parentBlocks.forEach((parentID) => {
let parentBlock = getBlock( parentID );
if ( ! groupQueryBlock ) {
groupQueryBlock = 'core/group' === parentBlock.name && parentBlock.attributes.canvasPostsQuery ? parentBlock : false;
}
});
}
if ( groupQueryBlock ) {
return (
<Fragment>
<Notice
status="warning"
isDismissible={ false }
className="cnvs-extension-group-posts-query-notice"
>
{ __( 'This post block is located inside the "Group" block with enabled global Query settings. Please, change these settings in the parent "Group" block.', 'canvas' ) }
</Notice>
<Button
isPrimary
onClick={ () => {
selectBlock( groupQueryBlock.clientId );
} }
>
{ __( 'Select "Group" Block', 'canvas' ) }
</Button>
</Fragment>
);
}
return sectionFields;
}
/**
* Control the `group_query` attribute in Posts blocks.
*
* @param {function|Component} BlockEdit Original component.
*
* @return {string} Wrapped component.
*/
const withGroupAttribute = createHigherOrderComponent( ( OriginalComponent ) => {
class CanvasPostsQueryGroupController extends Component {
constructor() {
super( ...arguments );
this.update = this.update.bind( this );
}
componentDidMount() {
this.update();
}
componentDidUpdate() {
this.update();
}
update() {
const {
name,
groupQueryBlock,
attributes,
setAttributes,
} = this.props;
if ( 'canvas/posts' !== name ) {
return;
}
if ( groupQueryBlock ) {
if ( groupQueryBlock.attributes.canvasClassName && groupQueryBlock.attributes.canvasClassName !== attributes.queryGroup ) {
setAttributes( {
queryGroup: groupQueryBlock.attributes.canvasClassName,
} );
}
} else if ( attributes.queryGroup ) {
setAttributes( {
queryGroup: '',
} );
}
}
render() {
return <OriginalComponent { ...this.props } />;
}
}
return compose(
withSelect((select, ownProps) => {
if ( 'canvas/posts' !== ownProps.name ) {
return {};
}
const {
getBlockHierarchyRootClientId,
getBlockParents,
getBlock,
} = select('core/block-editor');
const rootBlock = getBlock( getBlockHierarchyRootClientId( ownProps.clientId ) );
const parentBlocks = getBlockParents(ownProps.clientId);
let groupQueryBlock = false;
// Trying to find parent Posts Group block.
if ( rootBlock && (undefined === typeof rootBlock.attributes.ref || ! rootBlock.attributes.ref) && parentBlocks ) {
parentBlocks.forEach((parentID) => {
let parentBlock = getBlock( parentID );
if ( ! groupQueryBlock ) {
groupQueryBlock = 'core/group' === parentBlock.name && parentBlock.attributes.canvasPostsQuery ? parentBlock : false;
}
});
}
return {
groupQueryBlock,
};
})
)(CanvasPostsQueryGroupController);
}, 'withGroupAttribute' );
addFilter( 'canvas.component.fieldsRender', 'canvas/group-block-posts-query/hide-posts-query', hidePostsQuery );
addFilter( 'editor.BlockEdit', 'canvas/group-block-posts-query/additional-attributes', withGroupAttribute );
@@ -0,0 +1,207 @@
/**
* Internal dependencies
*/
import isCoreBlockWithExt from '../../utils/is-core-block-with-ext';
import dynamicStylesBYkey from '../../utils/dynamic-styles-by-key';
const {
canvasBreakpoints,
} = window;
/**
* WordPress dependencies
*/
const { __ } = wp.i18n;
const {
addFilter,
} = wp.hooks;
const {
Component,
Fragment,
} = wp.element;
const {
createHigherOrderComponent,
} = wp.compose;
const { InspectorControls } = wp.blockEditor;
const {
PanelBody,
ToggleControl,
} = wp.components;
const {
hasBlockSupport,
} = wp.blocks;
/**
* Extend block attributes with spacings.
*
* @param {Object} blockSettings Original block settings.
* @param {String} name Original block name.
*
* @return {Object} Filtered block settings.
*/
function addAttribute( blockSettings, name ) {
let supports = hasBlockSupport( name, 'canvasResponsive', false );
// add support to core blocks
if ( isCoreBlockWithExt( name ) ) {
blockSettings.supports = {
...blockSettings.supports,
canvasResponsive: true,
};
supports = true;
}
if ( supports ) {
if ( ! blockSettings.attributes ) {
blockSettings.attributes = {};
}
// responsive attributes.
Object.keys( canvasBreakpoints ).forEach( ( breakpoint ) => {
if ( ! blockSettings.attributes[ `canvasResponsiveHide_${ breakpoint }` ] ) {
blockSettings.attributes[ `canvasResponsiveHide_${ breakpoint }` ] = {
type: 'boolean',
};
}
} );
}
return blockSettings;
}
/**
* Override the default edit UI to include a new block inspector control for
* assigning the custom spacings if needed.
*
* @param {function|Component} BlockEdit Original component.
*
* @return {string} Wrapped component.
*/
const withInspectorControl = createHigherOrderComponent( ( OriginalComponent ) => {
return class CanvasResponsiveWrapper extends Component {
constructor() {
super( ...arguments );
this.getResponsiveStyles = this.getResponsiveStyles.bind( this );
}
/**
* Prepare styles to `hide` block.
*
* @returns {String} responsive styles.
*/
getResponsiveStyles() {
const {
attributes,
clientId,
} = this.props;
const {
canvasClassName,
} = attributes;
let customStyles = '';
if ( canvasClassName ) {
const blockSelector = `#block-${ clientId }`;
Object.keys( canvasBreakpoints ).forEach( ( breakpoint, i ) => {
let media = '';
switch (breakpoint) {
case 'mobile':
media = `(max-width: ${ canvasBreakpoints['mobile'].width }px)`;
break;
case 'tablet':
media = `(min-width: ${ canvasBreakpoints['mobile'].width + 1 }px) and (max-width: ${ canvasBreakpoints['tablet'].width }px)`;
break;
case 'notebook':
media = `(min-width: ${ canvasBreakpoints['tablet'].width + 1 }px) and (max-width: ${ canvasBreakpoints['notebook'].width }px)`;
break;
case 'laptop':
media = `(min-width: ${ canvasBreakpoints['tablet'].width + 1 }px) and (max-width: ${ canvasBreakpoints['laptop'].width }px)`;
break;
case 'desktop':
media = `(min-width: ${ canvasBreakpoints['desktop'].width }px)`;
break;
}
if ( attributes[ 'canvasResponsiveHide_' + breakpoint ] ) {
customStyles += `
@media ${ media } {
${ blockSelector }::before {
background: rgba(0, 0, 0, 0.1) repeating-linear-gradient(125deg, rgba(0, 0, 0, 0.05), rgba(0, 0, 0, 0.05) 1px, transparent 2px, transparent 9px);
}
${ blockSelector } {
-webkit-filter: opacity(0.4) saturate(0);
filter: opacity(0.4) saturate(0);
}
}
`;
}
} );
}
return customStyles;
}
render() {
if ( ! hasBlockSupport( this.props.name, 'canvasResponsive', false ) ) {
return <OriginalComponent { ...this.props } />;
}
const {
attributes,
setAttributes,
} = this.props;
const {
canvasClassName,
} = attributes;
dynamicStylesBYkey( 'responsive', canvasClassName, this.getResponsiveStyles() );
// add new spacings controls.
return (
<Fragment>
<OriginalComponent
{ ...this.props }
{ ...this.state }
setState={ this.setState }
/>
<InspectorControls>
<PanelBody
title={ __( 'Responsive Settings' ) }
initialOpen={ false }
>
{ Object.keys( canvasBreakpoints ).map( ( breakpoint ) => {
return (
<ToggleControl
key={ breakpoint }
label={ __( 'Hide On ' ) + canvasBreakpoints[breakpoint].label }
checked={ attributes[ `canvasResponsiveHide_${ breakpoint }` ] }
onChange={ () => {
setAttributes( {
[ `canvasResponsiveHide_${ breakpoint }` ]: ! attributes[ `canvasResponsiveHide_${ breakpoint }` ],
} );
} }
/>
);
} ) }
</PanelBody>
</InspectorControls>
</Fragment>
);
}
}
}, 'withInspectorControl' );
// Init filters.
addFilter( 'blocks.registerBlockType', 'canvas/responsive-settings/additional-attributes', addAttribute );
addFilter( 'editor.BlockEdit', 'canvas/responsive-settings/additional-attributes', withInspectorControl );
@@ -0,0 +1,298 @@
/**
* Internal dependencies
*/
import isCoreBlockWithExt from '../../utils/is-core-block-with-ext';
import dynamicStylesBYkey from '../../utils/dynamic-styles-by-key';
import ComponentSpacings from '../../components/spacings';
import ComponentResponsiveWrapper from '../../components/responsive-wrapper';
const {
canvasBreakpoints,
} = window;
/**
* WordPress dependencies
*/
const { __ } = wp.i18n;
const {
addFilter,
} = wp.hooks;
const {
Component,
Fragment,
} = wp.element;
const {
createHigherOrderComponent,
} = wp.compose;
const { InspectorControls } = wp.blockEditor;
const {
PanelBody,
} = wp.components;
const {
hasBlockSupport,
} = wp.blocks;
const allSpacings = {
'marginTop': 'margin-top',
'marginBottom': 'margin-bottom',
'marginLeft': 'margin-left',
'marginRight': 'margin-right',
'paddingTop': 'padding-top',
'paddingBottom': 'padding-bottom',
'paddingLeft': 'padding-left',
'paddingRight': 'padding-right',
};
/**
* Extend block attributes with spacings.
*
* @param {Object} blockSettings Original block settings.
* @param {String} name Original block name.
*
* @return {Object} Filtered block settings.
*/
function addAttribute( blockSettings, name ) {
let supports = hasBlockSupport( name, 'canvasSpacings', false );
// add support to core blocks
if ( isCoreBlockWithExt( name ) ) {
blockSettings.supports = {
...blockSettings.supports,
canvasSpacings: true,
};
supports = true;
}
if ( supports ) {
if ( ! blockSettings.attributes ) {
blockSettings.attributes = {};
}
// responsive attributes.
Object.keys( canvasBreakpoints ).forEach( ( breakpoint ) => {
let suffix = '';
if ( breakpoint && 'desktop' !== breakpoint ) {
suffix = `_${ breakpoint }`;
}
// spacings.
Object.keys( allSpacings ).forEach( ( spacing ) => {
if ( ! blockSettings.attributes[ spacing + suffix ] ) {
blockSettings.attributes[ spacing + suffix ] = {
type: 'number',
};
}
} );
// link.
if ( ! blockSettings.attributes[ `marginLink${ suffix }` ] ) {
blockSettings.attributes[ `marginLink${ suffix }` ] = {
type: 'boolean',
};
}
if ( ! blockSettings.attributes[ `paddingLink${ suffix }` ] ) {
blockSettings.attributes[ `paddingLink${ suffix }` ] = {
type: 'boolean',
};
}
// unit.
if ( ! blockSettings.attributes[ `marginUnit${ suffix }` ] ) {
blockSettings.attributes[ `marginUnit${ suffix }` ] = {
type: 'string',
};
}
if ( ! blockSettings.attributes[ `paddingUnit${ suffix }` ] ) {
blockSettings.attributes[ `paddingUnit${ suffix }` ] = {
type: 'string',
};
}
} );
}
return blockSettings;
}
/**
* Override the default edit UI to include a new block inspector control for
* assigning the custom spacings if needed.
*
* @param {function|Component} BlockEdit Original component.
*
* @return {string} Wrapped component.
*/
const withInspectorControl = createHigherOrderComponent( ( OriginalComponent ) => {
return class CanvasSpacingsWrapper extends Component {
constructor() {
super( ...arguments );
this.getCurrentSpacingStyles = this.getCurrentSpacingStyles.bind( this );
this.getSpacing = this.getSpacing.bind( this );
}
/**
* Get current spacing styles for preview purposes.
*
* @returns {String} spacing styles.
*/
getCurrentSpacingStyles() {
const {
attributes,
} = this.props;
const {
canvasClassName,
} = attributes;
let customStyles = '';
if ( canvasClassName ) {
Object.keys( canvasBreakpoints ).forEach( ( name ) => {
let suffix = '';
let marginUnit = 'px';
let paddingUnit = 'px';
let breakpointStyles = '';
if ( name && name !== 'desktop' ) {
suffix = '_' + name;
}
if ( attributes[ 'marginUnit' + suffix ] ) {
marginUnit = attributes[ 'marginUnit' + suffix ];
}
if ( attributes[ 'paddingUnit' + suffix ] ) {
paddingUnit = attributes[ 'paddingUnit' + suffix ];
}
Object.keys( allSpacings ).forEach( ( spacing ) => {
if ( typeof attributes[ spacing + suffix ] !== 'undefined' ) {
const currentUnit = /^margin/g.test( spacing ) ? marginUnit : paddingUnit;
breakpointStyles += `${ allSpacings[ spacing ] }: ${ attributes[ spacing + suffix ] }${ currentUnit }; `;
}
} );
if ( breakpointStyles ) {
breakpointStyles = `.${ canvasClassName } { ${ breakpointStyles } }`;
if ( suffix ) {
breakpointStyles = `@media (max-width: ${ canvasBreakpoints[ name ].width }px) { ${ breakpointStyles } } `;
}
customStyles += breakpointStyles;
}
} );
}
return customStyles;
}
/**
* Get current spacing value.
*
* @param {String} name - spacing name.
* @param {String} suffix - responsive suffix.
*
* @return {Int}
*/
getSpacing( name, suffix = '' ) {
const {
attributes,
} = this.props;
return attributes[ `${ name }${ suffix }` ];
}
render() {
if ( ! hasBlockSupport( this.props.name, 'canvasSpacings', false ) ) {
return <OriginalComponent { ...this.props } />;
}
const {
attributes,
setAttributes,
} = this.props;
const {
canvasClassName,
} = attributes;
dynamicStylesBYkey( 'spacings', canvasClassName, this.getCurrentSpacingStyles() );
// add new spacings controls.
return (
<Fragment>
<OriginalComponent
{ ...this.props }
{ ...this.state }
setState={ this.setState }
/>
<InspectorControls>
<PanelBody
title={ __( 'Spacings' ) }
initialOpen={ false }
>
<ComponentResponsiveWrapper>
{ ( { responsiveSuffix, ComponentResponsiveDropdown } ) => {
return (
<Fragment>
{ /* Margins */ }
<h2>
{ __( 'Margins' ) }
<ComponentResponsiveDropdown />
</h2>
<ComponentSpacings
prefix="margin"
suffix={ responsiveSuffix }
units={ [ 'px', '%' ] }
top={ this.getSpacing( 'marginTop', responsiveSuffix ) }
bottom={ this.getSpacing( 'marginBottom', responsiveSuffix ) }
left={ this.getSpacing( 'marginLeft', responsiveSuffix ) }
right={ this.getSpacing( 'marginRight', responsiveSuffix ) }
link={ this.getSpacing( 'marginLink', responsiveSuffix ) }
unit={ this.getSpacing( 'marginUnit', responsiveSuffix ) }
onChange={ ( attributes ) => {
setAttributes( attributes );
} }
/>
{ /* Paddings */ }
<h2>
{ __( 'Paddings' ) }
<ComponentResponsiveDropdown />
</h2>
<ComponentSpacings
prefix="padding"
suffix={ responsiveSuffix }
units={ [ 'px', 'em', '%' ] }
top={ this.getSpacing( 'paddingTop', responsiveSuffix ) }
bottom={ this.getSpacing( 'paddingBottom', responsiveSuffix ) }
left={ this.getSpacing( 'paddingLeft', responsiveSuffix ) }
right={ this.getSpacing( 'paddingRight', responsiveSuffix ) }
link={ this.getSpacing( 'paddingLink', responsiveSuffix ) }
unit={ this.getSpacing( 'paddingUnit', responsiveSuffix ) }
onChange={ ( attributes ) => {
setAttributes( attributes );
} }
/>
</Fragment>
);
} }
</ComponentResponsiveWrapper>
</PanelBody>
</InspectorControls>
</Fragment>
);
}
};
}, 'withInspectorControl' );
// Init filters.
addFilter( 'blocks.registerBlockType', 'canvas/spacings/additional-attributes', addAttribute );
addFilter( 'editor.BlockEdit', 'canvas/spacings/additional-attributes', withInspectorControl );
@@ -0,0 +1,288 @@
/**
* External dependencies
*/
import classnames from 'classnames';
/**
* Internal dependencies
*/
import isCoreBlockWithExt from '../../utils/is-core-block-with-ext';
import getParentBlock from '../../utils/get-parent-block';
const {
canvasBreakpoints,
} = window;
/**
* WordPress dependencies
*/
const {
addFilter,
} = wp.hooks;
const {
Component,
} = wp.element;
const {
createHigherOrderComponent,
} = wp.compose;
const {
hasBlockSupport,
} = wp.blocks;
const { select, subscribe } = wp.data;
/**
* Extend block attributes with unique class name.
*
* @param {Object} blockSettings Original block settings.
* @param {String} name Original block name.
*
* @return {Object} Filtered block settings.
*/
function addAttribute( blockSettings, name ) {
let supports = (
hasBlockSupport( blockSettings, 'canvasUniqueClass', false ) ||
hasBlockSupport( blockSettings, 'canvasBackgroundImage', false ) ||
hasBlockSupport( blockSettings, 'canvasSpacings', false ) ||
hasBlockSupport( blockSettings, 'canvasBorder', false ) ||
hasBlockSupport( blockSettings, 'canvasResponsive', false )
);
// add support to core blocks
if ( isCoreBlockWithExt( name ) ) {
blockSettings.supports = {
...blockSettings.supports,
canvasUniqueClass: true,
};
supports = true;
}
if ( supports ) {
if ( blockSettings.attributes && ! blockSettings.attributes.canvasClassName ) {
blockSettings.attributes.canvasClassName = {
type: 'string',
};
}
}
return blockSettings;
}
/**
* Override the default edit UI to include a new block inspector control for
* assigning the custom spacings if needed.
*
* @param {function|Component} BlockEdit Original component.
*
* @return {string} Wrapped component.
*/
const withInspectorControl = createHigherOrderComponent( ( OriginalComponent ) => {
class CanvasUniqueClassNameWrapper extends Component {
constructor() {
super( ...arguments );
this.getAllBlocks = this.getAllBlocks.bind( this );
this.maybeCreateUniqueClassName = this.maybeCreateUniqueClassName.bind( this );
}
componentDidMount() {
this.maybeCreateUniqueClassName( true );
}
componentDidUpdate() {
this.maybeCreateUniqueClassName();
}
/**
* Get recursive all blocks of the current page
*/
getAllBlocks( blocks = false ) {
let result = [];
if ( ! blocks ) {
blocks = wp.data.select( 'core/block-editor' ).getBlocks();
}
if ( ! blocks ) {
return result;
}
blocks.forEach( ( data ) => {
result.push( data );
if ( data.innerBlocks && data.innerBlocks.length ) {
result = [
...result,
...this.getAllBlocks( data.innerBlocks ),
];
}
} );
return result;
}
/**
* Generate unique block class name
*/
maybeCreateUniqueClassName( checkDuplicates ) {
const {
name,
attributes,
setAttributes,
clientId,
} = this.props;
const {
getBlockHierarchyRootClientId,
getBlock,
} = select('core/block-editor');
const rootBlock = getBlock(getBlockHierarchyRootClientId(clientId));
if ( rootBlock && rootBlock.hasOwnProperty( 'name' ) && 'core/gallery' === rootBlock.name ) {
return;
}
if (
! hasBlockSupport( name, 'canvasUniqueClass', false ) &&
! hasBlockSupport( name, 'canvasBackgroundImage', false ) &&
! hasBlockSupport( name, 'canvasSpacings', false ) &&
! hasBlockSupport( name, 'canvasBorder', false ) &&
! hasBlockSupport( name, 'canvasResponsive', false )
) {
return;
}
let {
canvasClassName,
} = attributes;
// prevent unique ID duplication after block duplicated.
if ( checkDuplicates ) {
const allBlocks = this.getAllBlocks();
allBlocks.forEach( ( data ) => {
if (
data.clientId !== clientId &&
data.attributes &&
data.attributes.canvasClassName &&
data.attributes.canvasClassName === canvasClassName
) {
canvasClassName = '';
}
} );
}
if ( ! canvasClassName ) {
const newId = new Date().getTime();
// Generated HTML classes for blocks follow the `cnvs-block-{name}` nomenclature.
// Blocks provided by Canvas drop the prefixes 'canvas/'.
const className = 'cnvs-block-' + name.replace( /\//, '-' ).replace( /^canvas-/, '' );
setAttributes( {
canvasClassName: className + '-' + newId,
} );
}
}
render() {
return <OriginalComponent { ...this.props } />;
}
}
return CanvasUniqueClassNameWrapper;
}, 'withInspectorControl' );
/**
* Override props assigned to save component to inject custom styles.
* This is only applied if the block's save result is an
* element and not a markup string.
*
* @param {Object} extraProps Additional props applied to save element.
* @param {Object} blockType Block type.
* @param {Object} attributes Current block attributes.
*
* @return {Object} Filtered props applied to save element.
*/
function addSaveProps( extraProps, blockType, attributes ) {
// add custom classname to non-canvas blocks.
// we need this class only when Spacings or Responsive controls added on the block.
if ( blockType.name && ! /^canvas/.test( name ) && attributes.canvasClassName ) {
const extAttrs = [
'backgroundImage',
'backgroundPosition',
'backgroundPositionXUnit',
'backgroundPositionXVal',
'backgroundPositionYUnit',
'backgroundPositionYVal',
'backgroundAttachment',
'backgroundRepeat',
'backgroundSize',
'backgroundSizeUnit',
'backgroundSizeVal',
'marginTop',
'marginRight',
'marginBottom',
'marginLeft',
'paddingTop',
'paddingRight',
'paddingBottom',
'paddingLeft',
'borderRadiusTopLeft',
'borderRadiusTopRight',
'borderRadiusBottomLeft',
'borderRadiusBottomRight',
'borderStyle',
'canvasResponsiveHide',
];
let isCustomClassRequired = false;
// responsive attributes.
extAttrs.forEach( ( attr ) => {
if ( typeof attributes[ attr ] !== 'undefined' ) {
isCustomClassRequired = true;
}
Object.keys( canvasBreakpoints ).forEach( ( breakpoint ) => {
if ( ! isCustomClassRequired && typeof attributes[ `${ attr }_${ breakpoint }` ] !== 'undefined' ) {
isCustomClassRequired = true;
}
} );
} );
if ( isCustomClassRequired ) {
extraProps.className = classnames(
extraProps.className,
attributes.canvasClassName
);
}
}
return extraProps;
}
// Init filters.
addFilter( 'blocks.registerBlockType', 'canvas/unique-classname/additional-attributes', addAttribute );
addFilter( 'editor.BlockEdit', 'canvas/unique-classname/additional-attributes', withInspectorControl );
addFilter( 'blocks.getSaveContent.extraProps', 'canvas/unique-classname/save-props', addSaveProps );
/**
* Used to modify the blocks wrapper component containing the blocks edit
* component and all toolbars. It receives the original BlockListBlock
* component and returns a new wrapped component.
*/
const withClientIdClassName = createHigherOrderComponent( ( BlockListBlock ) => {
return ( props ) => {
if ( props.name && ! /^canvas/.test( props.name ) && props.attributes.canvasClassName ) {
return <BlockListBlock { ...props } className={ classnames( props.className, props.attributes.canvasClassName ) } />;
} else {
return <BlockListBlock { ...props } />;
}
};
}, 'withClientIdClassName' );
addFilter( 'editor.BlockListBlock', 'canvas/with-client-id-class-name', withClientIdClassName );
File diff suppressed because one or more lines are too long
@@ -0,0 +1,32 @@
/**
* Internal dependencies
*/
import './store/scheme';
import './store/breakpoint';
import './extensions/group-block-posts-query';
import './extensions/unique-class';
import './extensions/background-image';
import './extensions/spacings';
import './extensions/border';
import './extensions/responsive-settings';
import './style.scss';
/**
* WordPress dependencies
*/
const {
addFilter,
} = wp.hooks;
/**
* Prepare server render attributes.
*
* @param {Object} attrs Values of attributes.
* @param {Object} data Fields data.
* @returns {Object}
*/
function prepare_server_render_attributes( attrs, data ) {
return attrs;
}
addFilter( 'canvas.block.prepareServerRenderAttributes', 'canvas.block.prepareServerRenderAttributes', prepare_server_render_attributes );
File diff suppressed because one or more lines are too long
@@ -0,0 +1,78 @@
/**
* Internal dependencies
*/
import './style.scss';
/**
* WordPress dependencies
*/
const {
jQuery: $,
canvasLocalize,
} = window;
const {
__,
} = wp.i18n;
/**
* Add schemes button to Gutenberg settings
*/
$( document ).on( 'DOMContentLoaded', () => {
wp.data.subscribe(function () {
setTimeout(function () {
if (!document.getElementById('canvas-settings-toogle-scheme')) {
const $settings = $( '.edit-post-header__settings' );
if ( $settings.length ) {
$settings.find( '.edit-post-more-menu' ).before( `<div id="canvas-settings-toogle-scheme" class="canvas-settings-toogle-scheme">
<button class="components-button components-icon-button" aria-label="${ __( 'Scheme', 'canvas' ) }">
<svg class="canvas-default" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line><line x1="1" y1="12" x2="3" y2="12"></line><line x1="21" y1="12" x2="23" y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line></svg>
<svg class="canvas-dark" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg>
</button>
</div>` );
}
}
}, 1)
});
// Subscribe change scheme.
wp.data.subscribe( function() {
var scheme = wp.data.select( 'canvas/scheme' ).getScheme();
$('.canvas-settings-toogle-scheme').attr( 'scheme', scheme );
// Alt version.
$('.block-editor-writing-flow').attr( 'site-data-scheme', scheme );
// New version.
$('.block-editor-writing-flow').attr( 'data-site-scheme', scheme );
if ( 'dark' === scheme ) {
$('.block-editor-writing-flow').attr( 'data-scheme', canvasLocalize.schemeDarkSlug );
} else {
$('.block-editor-writing-flow').attr( 'data-scheme', canvasLocalize.schemeDefaultSlug );
}
} );
// Change global scheme.
$( document ).on( 'click', '.canvas-settings-toogle-scheme', ( e ) => {
e.preventDefault();
$('.block-editor-writing-flow').addClass( 'canvas-sceme-toggled' );
var scheme = wp.data.select( 'canvas/scheme' ).getScheme();
if ( 'dark' === scheme ) {
wp.data.dispatch('canvas/scheme').updateScheme('');
} else {
wp.data.dispatch('canvas/scheme').updateScheme('dark');
}
setTimeout( () => {
$('.block-editor-writing-flow').removeClass( 'canvas-sceme-toggled' );
}, 100 );
} );
} );
@@ -0,0 +1,7 @@
export function updateBreakpoint( breakpoint ) {
return {
type: 'UPDATE_BREAKPOINT',
breakpoint,
};
}
@@ -0,0 +1,15 @@
/**
* WordPress dependencies
*/
const {
registerStore,
} = wp.data;
/**
* Internal dependencies
*/
import reducer from './reducer';
import * as selectors from './selectors';
import * as actions from './actions';
registerStore( 'canvas/breakpoint', { reducer, selectors, actions } );
@@ -0,0 +1,15 @@
function reducer( state = { breakpoint: '' }, action ) {
switch ( action.type ) {
case 'UPDATE_BREAKPOINT':
return {
...state,
breakpoint: action.breakpoint,
};
// no default
}
return state;
}
export default reducer;
@@ -0,0 +1,4 @@
export function getBreakpoint( state, data ) {
return state.breakpoint;
}
@@ -0,0 +1,7 @@
export function updateScheme( scheme ) {
return {
type: 'UPDATE_SCHEME',
scheme,
};
}
@@ -0,0 +1,15 @@
/**
* WordPress dependencies
*/
const {
registerStore,
} = wp.data;
/**
* Internal dependencies
*/
import reducer from './reducer';
import * as selectors from './selectors';
import * as actions from './actions';
registerStore( 'canvas/scheme', { reducer, selectors, actions } );
@@ -0,0 +1,15 @@
function reducer( state = { scheme: '' }, action ) {
switch ( action.type ) {
case 'UPDATE_SCHEME':
return {
...state,
scheme: action.scheme,
};
// no default
}
return state;
}
export default reducer;
@@ -0,0 +1,4 @@
export function getScheme( state, data ) {
return state.scheme;
}
@@ -0,0 +1,54 @@
/**
* WordPress dependencies
*/
const TokenList = wp.tokenList;
/**
* Returns the active style from the given className.
*
* @param {string} className Class name
* @param {string} namespace The replacing class namespace.
*
* @return {string} The active style.
*/
export function getActiveClass( className, namespace ) {
const list = new TokenList( className );
for ( const activeClass of list.values() ) {
if ( activeClass.indexOf( `${ namespace }-` ) === -1 ) {
continue;
}
return activeClass;
}
return false;
}
/**
* Replaces the active class with namespace in the className.
*
* @param {string} className Class name.
* @param {string} namespace The replacing class namespace.
* @param {string} newClass The replacing class.
*
* @return {string} The updated className.
*/
export function replaceClass( className, namespace, newClass ) {
const list = new TokenList( className );
const namespaceRegExp = new RegExp( `${ namespace }-` );
// remove classes with the same namespace.
for ( const activeClass of list.values() ) {
if ( namespaceRegExp.test( activeClass ) ) {
list.remove( activeClass );
}
}
// add new class.
if ( newClass ) {
list.add( `${ namespace }-${ newClass }` );
}
return list.value;
}
@@ -0,0 +1,36 @@
/**
* Generating dynamic styles by key.
*
* @param {string} suffix suffix
* @param {string} key key
* @param {string} styles styles
*/
export default function dynamicStylesBYkey( suffix, key, styles ) {
if ( ! key || ! styles || 'undefined' === typeof( key ) ) {
return;
}
var styleID = `dynamic-css-${suffix}-${key}`;
if ( document.getElementById( styleID ) ) {
document.getElementById( styleID ).innerHTML = styles;
} else {
// Get the head element.
var head = document.head || document.getElementsByTagName('head')[0];
// Create a new style node.
var style = document.createElement( 'style' );
style.id = styleID;
// Set type attribute for the style node.
style.type = 'text/css';
// Append the css rules to the style node.
style.appendChild( document.createTextNode( styles ) );
// Append the style node to the head of the page.
head.appendChild(style);
}
}
@@ -0,0 +1,27 @@
/**
* Get parent block data.
*
* @param {Object} rootBlock root block data
* @param {Object} currentBlock current block data
* @return {Object}
*/
export default function getParentBlock(rootBlock, currentBlock) {
if (rootBlock && rootBlock.clientId === currentBlock.clientId) {
return rootBlock;
}
let result = false;
// Check all inner blocks to find parent block.
if (rootBlock && rootBlock.innerBlocks && rootBlock.innerBlocks.length) {
rootBlock.innerBlocks.forEach((innerBlockData) => {
const innerParent = getParentBlock(innerBlockData, currentBlock);
if (!result && innerParent) {
result = innerParent.clientId === currentBlock.clientId ? rootBlock : innerParent;
}
});
}
return result;
}
@@ -0,0 +1,18 @@
/**
* Check if core block support extensions.
*
* @param {string} name block name
*
* @return {boolean}
*/
export default function isCoreBlockWithExt(name) {
return name &&
/^core/.test(name) &&
! /^core\/block$/.test(name) &&
! /^core\/tag-cloud/.test(name) &&
! /^core\/calendar/.test(name) &&
! /^core\/latest-comments/.test(name) &&
! /^core\/rss/.test(name) &&
! /^core\/archives/.test(name) &&
! /^core\/template/.test(name);
}
@@ -0,0 +1,129 @@
/**
* WordPress dependencies
*/
const {
applyFilters,
} = wp.hooks;
/**
* Check active_callback in custom controls.
*
* @param {Object} fieldData field data.
* @param {Object} attributes block attributes.
* @param {Object} allFields all fields.
*
* @returns {Boolean}
*/
export default function isFieldVisible( fieldData, attributes, allFields ) {
let result = true;
const filteredAttributes = applyFilters( 'canvas.block.prepareServerRenderAttributes', attributes, {
fields: allFields,
} );
if ( fieldData.active_callback && fieldData.active_callback.length ) {
result = checkCondition( fieldData.active_callback, filteredAttributes, 'AND' );
}
return result;
}
/**
* Compare 2 values
*
* @param {mixed} lval Left value.
* @param {string} operator Operator.
* @param {mixed} rval Right value.
*
* @returns {boolean}
*/
function compare( lval, operator, rval ) {
let checkResult = true;
switch ( operator ) {
case '==':
checkResult = lval == rval;
break;
case '===':
checkResult = lval === rval;
break;
case '!=':
checkResult = lval != rval;
break;
case '!==':
checkResult = lval !== rval;
break;
case '>=':
checkResult = lval >= rval;
break;
case '<=':
checkResult = lval <= rval;
break;
case '>':
checkResult = lval > rval;
break;
case '<':
checkResult = lval < rval;
break;
case 'AND':
checkResult = lval && rval;
break;
case 'OR':
checkResult = lval || rval;
break;
default:
checkResult = lval;
break;
}
return checkResult;
}
/**
* Check condition
*
* @param {Object} conditions - Conditions array.
* @param {Object} attributes - Available block attributes.
* @param {string} relation - Can be one of 'AND' or 'OR'.
*
* @returns {Boolean}
*/
function checkCondition( conditions, attributes, relation ) {
const childRelation = ( 'AND' === relation ) ? 'OR' : 'AND';
// by default result will be TRUE for relation AND
// and FALSE for relation OR.
let result = relation === 'AND';
conditions.forEach( ( data ) => {
if ( Array.isArray( data ) ) {
result = compare( result, relation, checkCondition( data, attributes, childRelation ) );
} else if ( data.field ) {
const splitValName = data.field.split( '.' );
let fieldVal = undefined;
// check for array values like:
// toggleListName['option1']
if ( 2 === splitValName.length && typeof attributes[ splitValName[ 0 ] ] !== 'undefined' && typeof attributes[ splitValName[ 0 ] ][ splitValName[ 1 ] ] !== 'undefined' ) {
fieldVal = attributes[ splitValName[ 0 ] ][ splitValName[ 1 ] ];
}
// check for normal values
if ( typeof fieldVal === 'undefined' ) {
fieldVal = attributes[ data.field ];
}
// check count
if ( typeof data.count !== 'undefined' ) {
const count = fieldVal.split( data['count'] );
fieldVal = count.length - 1;
}
if ( typeof fieldVal !== 'undefined' ) {
result = compare( result, relation, compare( fieldVal, data.operator, data.value ) );
}
}
} );
return result;
}
@@ -0,0 +1,135 @@
<?php
/**
* Check if field visible.
*
* @package Canvas
*/
if ( ! class_exists( 'CNVS_Gutenberg_Utils_Is_Field_Visible' ) ) {
/**
* Class Gutenberg Utils Is Field Visible.
*/
class CNVS_Gutenberg_Utils_Is_Field_Visible {
/**
* Check active_callback in custom controls.
*
* @param array $fieldData field data.
* @param array $attributes block attributes.
* @param array $allFields all fields.
* @param array $prepareAttrs prepare attributes for server render.
*
* @return boolean
*/
public static function check( $fieldData, $attributes, $allFields, $prepareAttrs = true ) {
$result = true;
// if ( $prepareAttrs ) {
// $attributes = apply_filters( 'canvas_block_prepare_server_render_attributes', $attributes, array( 'fields' => $allFields ) );
// }
if ( isset( $fieldData['active_callback'] ) ) {
$result = self::checkCondition( $fieldData['active_callback'], $attributes, 'AND' );
}
return $result;
}
/**
* Compare 2 values
*
* @param mixed $lval Left value.
* @param string $operator Operator.
* @param mixed $rval Right value.
*
* @return boolean
*/
public static function compare( $lval, $operator, $rval ) {
$check_result = true;
switch ( $operator ) {
case '==':
$check_result = $lval == $rval;
break;
case '===':
$check_result = $lval === $rval;
break;
case '!=':
$check_result = $lval != $rval;
break;
case '!==':
$check_result = $lval !== $rval;
break;
case '>=':
$check_result = $lval >= $rval;
break;
case '<=':
$check_result = $lval <= $rval;
break;
case '>':
$check_result = $lval > $rval;
break;
case '<':
$check_result = $lval < $rval;
break;
case 'AND':
$check_result = $lval && $rval;
break;
case 'OR':
$check_result = $lval || $rval;
break;
default:
$check_result = $lval;
break;
}
return $check_result;
}
/**
* Check condition
*
* @param array $conditions - Conditions array.
* @param array $attributes - Available block attributes.
* @param string $relation - Can be one of 'AND' or 'OR'.
*
* @return boolean
*/
public static function checkCondition( $conditions, $attributes, $relation ) {
$childRelation = ( 'AND' === $relation ) ? 'OR' : 'AND';
// By default result will be TRUE for relation AND and FALSE for relation OR.
$result = $relation === 'AND';
foreach ( $conditions as $data ) {
if ( is_array( $data ) && ! isset( $data['field'] ) ) {
$result = self::compare( $result, $relation, self::checkCondition( $data, $attributes, $childRelation ) );
} elseif ( isset( $data['field'] ) ) {
$splitValName = explode( '.', $data['field'] );
$fieldVal = null;
// Check for array values like: toggleListName['option1'].
if ( 2 === count( $splitValName ) && isset( $attributes[ $splitValName[0] ] ) && isset( $attributes[ $splitValName[0] ][ $splitValName[1] ] ) ) {
$fieldVal = $attributes[ $splitValName[0] ][ $splitValName[1] ];
}
// Check for normal values.
if ( null === $fieldVal && isset( $attributes[ $data['field'] ] ) ) {
$fieldVal = $attributes[ $data['field'] ];
}
// Check count.
if ( isset( $data['count'] ) ) {
$count = explode( $data['count'], $fieldVal );
$fieldVal = count( $count ) - 1;
}
if ( null !== $fieldVal ) {
$result = self::compare( $result, $relation, self::compare( $fieldVal, isset( $data['operator'] ) ? $data['operator'] : '===', isset( $data['value'] ) ? $data['value'] : true ) );
}
}
}
return $result;
}
}
}