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,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 );