import FAIcons from './icons.json';
function initCaxton( $, blocks, el, i18n, components ) {
const editor = wp.editor;
const __ = i18n.__;
const registerBlockType = blocks.registerBlockType;
const processTemplate = ( tpl, props ) => {
let ki;
for ( ki in props ) {
if ( props.hasOwnProperty( ki ) ) {
tpl = tpl.split( `{{${ki}}}` ).join( props[ ki ] );
}
}
// Twice to populate references in values
for ( ki in props ) {
if ( props.hasOwnProperty( ki ) ) {
tpl = tpl.split( `{{${ki}}}` ).join( props[ ki ] );
}
}
return tpl;
};
const caxtonCopy = ( target, obj ) => {
for ( var ki in obj ) {
if ( obj.hasOwnProperty( ki ) ) target[ ki ] = obj[ ki ];
}
return target;
};
const elementFromHTML = (html, props, tag) => {
if ( ! props ) {
props = {};
}
if ( ! tag ) {
tag = 'div'
}
const _props = $.extend( {
dangerouslySetInnerHTML: { __html: html },
}, props );
return el( tag, _props );
};
const HTMLFromElement = els => {
let content = '';
if ( els ) {
if ( ! els.length ) {
els = [els];
}
for ( let i = 0; i < els.length; i ++ ) {
const node = els[i];
switch ( typeof node ) {
case 'object':
content += wp.element.renderToString( node );
break;
default:
content += node;
}
}
}
return content;
};
class CxB {
constructor(block) {
const th = this;
this.keySuffix = 0;
if ( ! block.id ) {
console.error( 'Parameter `id` is required for CaxtonBlock' )
}
this.block = $.extend( {
title: block.id,
icon: 'star-filled',
category: 'layout',
fields: {},
attributes: {},
}, block );
th.tpl = block.tpl;
if ( block.toolbars ) {
block.fields = jQuery.extend( block.fields, block.toolbars )
} else {
block.toolbars = {};
}
th.fields = th.processFields( block.fields );
th.sections = block.sections ? block.sections : {};
th.sectionsFields = th.processSections( th.fields );
th.toolbars = th.processFields( block.toolbars );
th.registerBlock();
}
processFields(fields) {
const ret = [];
for ( const id in fields ) {
if( fields.hasOwnProperty( id ) ) {
const type = fields[id];
let field = {};
if ( typeof type === 'object' ) {
field = type;
} else {
field.type = type;
}
field.id = id;
field.label = field.label ? field.label : id;
if ( field.type === 'checkbox' || field.type === 'toggle' ) {
field.value = field.value || 1;
}
if ( ! field.default && isNaN( field.default ) ) {
field.default = '';
}
if ( field.attr ) {
this.block.attributes[id] = field.attr;
} else {
this.block.attributes[id] = {
type: this.fieldAttrType( field ),
};
}
ret.push( field );
}
}
return ret;
}
fieldAttrType({type}) {
const attrTypeByFieldType = {
'number': 'number',
'range': 'number',
};
if ( attrTypeByFieldType[ type ] ) {
return attrTypeByFieldType[ type ];
}
return 'string';
}
processSections(fields) {
const sections = {};
for ( let i = 0; i < fields.length; i ++ ) {
const section = fields[i].section;
if ( section ) {
if ( ! sections[ section ] ) {
sections[ section ] = [];
}
sections[ section ].push( fields[i] )
}
}
return sections;
}
// region Inspector Fields
fieldProps(field, index) {
const id = field.id;
const that = this;
const fieldProps = $.extend( {}, field );
fieldProps.key = `${fieldProps.type}-${index}`;
fieldProps.value = that.attrs[ id ];
fieldProps.onChange = (val, moreValues) => {
const attrs = {};
attrs[ id ] = val;
if ( field.type === 'checkbox' || field.type === 'toggle' ) {
attrs[ id ] = val ? field.value : '';
}
that.focussedProps.setAttributes( attrs );
if ( typeof field.onChange === 'function' ) {
field.onChange( val, that, moreValues );
}
};
delete fieldProps.id;
delete fieldProps.type;
return fieldProps;
}
imageFieldEl(field, index) {
const props = this.fieldProps( field, index );
if ( ! props.className ) {
props.className = '';
}
let btnContent = __( 'Select image' );
let removeBtn = null;
if ( props.value ) {
btnContent = [
el( 'img', {src: props.value, key: 'image'} ),
__( 'Click the image to edit or update' ),
];
removeBtn = el(
'a', {
className: 'caxton-remove-image',
href: '#',
onClick() {
props.onChange( '', {} )
},
},
el( 'i', {className: 'dashicons dashicons-no',} ),
'Remove'
);
}
props.className += ' caxton-image-picker';
return el(
components.BaseControl,
props,
el(
editor.MediaUpload,
{
onSelect(media) {
props.onChange( media.url, media );
},
type: 'image',
value: props.value,
label: props.label,
render({open}) {
return el( 'div', {},
removeBtn,
el( components.Button, {
className: props.value ? 'image-button' : 'button button-large',
onClick: open,
},
btnContent
)
);
},
}
)
);
}
colorFieldEl(field, index) {
const panelChildren = [];
const props = this.fieldProps( field, index );
props.title = props.label;
if ( props.initialOpen === undefined ) {
props.initialOpen = props.value ? false : true;
}
if ( field.help ) {
panelChildren.push( field.help );
}
props.colorSettings = [ {
label: props.label,
value: props.value,
onChange: props.onChange,
} ];
return el( wp.editor.PanelColorSettings, props, panelChildren )
}
checkboxFieldEl(field, index) {
const fieldProps = this.fieldProps( field, index );
fieldProps.checked = !! this.attrs[ field.id ];
return el( components.CheckboxControl, fieldProps );
}
radioFieldEl(field, index) {
const fieldProps = this.fieldProps( field, index );
fieldProps.selected = fieldProps.value;
return el( components.RadioControl, fieldProps );
}
rangeFieldEl(field, index) {
return el(
components.RangeControl,
this.fieldProps( field, index )
)
}
selectFieldEl(field, index) {
return el(
components.SelectControl,
this.fieldProps( field, index )
)
}
orderedSelectFieldEl(field, index) {
const
props = this.fieldProps( field, index ),
delimiter = props.delimiter ? props.delimiter : ',',
multiple = typeof props.multiple === 'undefined' ? true : props.multiple,
selectedOptionsData = {},
selectedOptions = [],
availableOption = [];
let
opt, optEl,
controlValue = props.value ? props.value.split( delimiter ) : [];
for ( var i = 0; i < props.options.length; i ++ ) {
opt = props.options[i];
optEl = el(
'div',
{
className: 'caxton-orderedselect-option',
'data-val': opt.value,
key: `option-${opt.value}`,
},
(
opt.image ? el( 'img', {src: opt.image} ) : null
),
opt.label
);
if ( typeof opt.value === 'number' ) {
opt.value = opt.value.toString();
}
if ( !controlValue.includes(opt.value) ) {
availableOption.push( optEl );
} else {
selectedOptionsData[ opt.value ] = opt;
}
}
for ( var i = 0; i < controlValue.length; i ++ ) {
opt = selectedOptionsData[controlValue[i]];
optEl = el(
'div',
{
className: 'caxton-orderedselect-option',
'data-val': opt.value,
key: `option-${opt.value}`,
},
(
opt.image ? el( 'img', {src: opt.image} ) : null
),
opt.label
);
selectedOptions.push( optEl );
}
if ( ! selectedOptions.length ) {
selectedOptions.push( el( 'span', {
className: 'caxton-placeholder o70',
key: 'placeholder',
}, 'Please choose...' ) )
}
selectedOptions.push( el( 'i', {
className: 'dashicons dashicons-arrow-down',
key: 'down-arrow-icon',
} ) );
return el(
components.BaseControl,
props,
el(
'div',
{
className: 'caxton-orderedselect-wrap',
key: 'orderedselect-wrap',
},
el( 'div', {
className: 'caxton-orderedselect-selected',
key: 'selected-options',
onClick({target}) {
let val;
const $target = $( target );
if ( $target.hasClass( 'caxton-orderedselect-option' ) ) {
val = $target.attr( 'data-val' );
controlValue.splice( controlValue.indexOf( val ), 1 );
props.onChange( controlValue.join( delimiter ) );
} else {
$target.closest( '.caxton-orderedselect-wrap' ).toggleClass( 'caxton-orderedselect-open' );
}
},
}, selectedOptions ),
el( 'div', {
className: 'caxton-orderedselect-available',
key: 'available-options',
onClick({target}) {
let val;
const $target = $( target );
if ( $target.hasClass( 'caxton-orderedselect-option' ) ) {
val = $target.attr( 'data-val' );
multiple ? controlValue.push( val ) : ( controlValue = [ val ] );
props.onChange( controlValue.join( delimiter ) );
}
},
}, availableOption )
),
);
}
fontFieldEl(field, index) {
if ( ! field.tpl ) {
field.tpl = 'font-family: %s;';
}
const props = this.fieldProps( field, index );
const onChange = props.onChange;
props.onChange = val => {
if ( !val.includes(',') ) {
const link = $( "" );
link.attr( "href", `https://fonts.googleapis.com/css?family=${val}` )
$( 'body' ).append( link );
}
onChange( val );
};
props.options = caxton.fonts;
return el(
components.SelectControl,
props
)
}
textFieldEl(field, index) {
return el(
components.TextControl,
this.fieldProps( field, index )
)
}
textareaFieldEl(field, index) {
return el(
components.TextareaControl,
this.fieldProps( field, index )
)
}
toggleFieldEl(field, index) {
const fieldProps = this.fieldProps( field, index );
fieldProps.checked = !! this.attrs[ field.id ];
return el( components.ToggleControl, fieldProps );
}
iconFieldEl(field, index) {
const props = this.fieldProps( field, index );
const that = this;
const defaultIcons = [];
props.title = props.label;
props.className = 'caxton-icon-picker-panel';
for ( let i = 0; i < 100; i ++ ) {
const ico = caxton.fontAwesome[i];
defaultIcons.push( el( 'i', {className: `fas fa-${ico.n}`, key: ico.n, title: ico.n.replace( ' fab', '' ) } ) );
}
defaultIcons.push( el( 'p', {key: 'helptext'}, 'Search icons for more from all Font Awesome icons' ) );
return el(
components.PanelBody,
props,
el( 'div', {
className: 'caxton-icon-picker',
onClick({target}) {
if ( target.tagName === 'I' ) {
props.onChange( ` ${target.className.replace( ' o-70', '' )}` );
}
}
},
el( 'input', {
type: 'text',
placeholder: __( 'Search icons', 'caxton' ),
onKeyUp({target}) {
let searchTerm = target.value;
let iconsMatched = 0;
let $wrp;
searchTerm = searchTerm.toLowerCase();
$wrp = $( target ).siblings( '.caxton-matching-icons' );
$wrp.html( '' );
for ( let i = 0; iconsMatched < 50 && i < caxton.fontAwesome.length; i ++ ) {
const ico = caxton.fontAwesome[i];
if ( ico.n.includes(searchTerm) ) {
iconsMatched ++;
$wrp.append( `` )
} else if ( iconsMatched < 34 && ico.s.includes(searchTerm) ) {
iconsMatched ++;
$wrp.append( `` )
}
}
}
} ),
el( 'span', {
className: 'dashicons dashicons-search',
title: __( 'Search', 'caxton' ),
} ),
el( 'span', {
className: 'dashicons dashicons-no',
title: __( 'Remove icon', 'caxton' ),
style: {
cursor: 'pointer',
display: props.value ? 'block' : 'none',
},
onClick() {
props.onChange( '' );
}
} ),
el( 'div', {
className: 'caxton-matching-icons',
onClick({target}) {
if ( target.tagName === 'I' ) {
props.onChange( ` ${target.className.replace( ' o-70', '' )}` );
}
}
}, defaultIcons )
)
);
}
positionFieldEl(field, index) {
const fieldProps = this.fieldProps( field, index );
fieldProps.selected = fieldProps.value;
fieldProps.options = [
// {value: 'left top', label: 'Left top',},
{value: 'center top', label: 'Top',},
// {value: 'right top', label: 'Right top',},
// {value: 'left center', label: 'Left center',},
{value: '', label: 'Center',},
// {value: 'right center', label: 'Right center',},
// {value: 'left bottom', label: 'Left bottom',},
{value: 'center bottom', label: 'Bottom',},
// {value: 'right bottom', label: 'Right bottom',},
]
return el( components.RadioControl, fieldProps );
}
AlignmentToolbarInit(field, index) {
const props = this.fieldProps( field, index );
props.controls = [
{
icon: 'editor-alignleft',
title: __( 'Align left' ),
isActive: props.value === ' tl',
onClick() {
props.onChange( ' tl' );
}
},
{
icon: 'editor-aligncenter',
title: __( 'Align center' ),
isActive: props.value === ' tc',
onClick() {
props.onChange( ' tc' );
}
},
{
icon: 'editor-alignright',
title: __( 'Align right' ),
isActive: props.value === ' tr',
onClick() {
props.onChange( ' tr' );
}
},
];
props.wideControlsEnabled = true;
return el(
components.Toolbar,
props
)
}
BlockWidthToolbarInit(field, index) {
const props = this.fieldProps( field, index );
props.controls = [
{
icon: 'align-center',
title: __( 'Default' ),
isActive: ! props.value,
onClick() {
props.onChange( '' );
}
},
{
icon: 'align-wide',
title: __( 'Wide width' ),
isActive: props.value === ' vw-100-bg',
onClick() {
props.onChange( ' vw-100-bg' );
}
},
{
icon: 'align-full-width',
title: __( 'Full width' ),
isActive: props.value === ' vw-100',
onClick() {
props.onChange( ' vw-100' );
}
},
];
props.wideControlsEnabled = true;
return el(
components.Toolbar,
props
)
}
BlockAlignToolbarInit(field, index) {
const props = this.fieldProps( field, index );
props.controls = [
{
icon: 'align-left',
title: __( 'Align left' ),
isActive: props.value === ' fl',
onClick() {
props.onChange( ' fl' );
}
},
{
icon: 'align-center',
title: __( 'Align center' ),
isActive: ! props.value,
onClick() {
props.onChange( '' );
}
},
{
icon: 'align-right',
title: __( 'Align right' ),
isActive: props.value === ' rl',
onClick() {
props.onChange( ' rl' );
}
},
];
props.wideControlsEnabled = true;
return el(
components.Toolbar,
props
)
}
// endregion
renderPanel(id) {
const fields = this.fields;
let panelProps = {};
let panelFields;
const th = this;
if ( th.sections[id] ) {
panelProps = th.sections[id];
}
panelProps = $.extend( panelProps, {
title: id,
className: '',
key: `CaxtonPanel${id}`,
initialOpen: false,
} );
panelProps.className += `caxton-section caxton-section-${id.toLowerCase().replace( /[^0-z]/g, '-' )}`;
panelFields = th.renderFields( th.sectionsFields[id], id );
return el( components.PanelBody, panelProps, panelFields );
}
fieldEl ( f, func, key_suffix ) {
if ( ! func ) {
func = f['type'] + 'FieldEl';
}
if ( 'undefined' === typeof key_suffix ) {
key_suffix = this.keySuffix++;
}
return this[ func ]( f, key_suffix );
}
renderFields(fields, section, functionSuffix) {
const els = [];
const panelsRenderd = [];
if ( ! functionSuffix ) {
functionSuffix = 'FieldEl';
}
for ( let i = 0; i < fields.length; i ++ ) {
const f = fields[i];
let func;
if ( functionSuffix.includes('Toolbar') ) {
f['type'] = f['type'].replace( 'Toolbar', '' )
}
func = f['type'] + functionSuffix;
if ( typeof this[ func ] === 'function' ) {
if ( ! f.hide ) {
if ( ! section ) {
if ( ! f.section ) {
els.push( this.fieldEl( f, func, i ) );
} else if ( !panelsRenderd.includes(f.section) ) {
panelsRenderd.push( f.section );
els.push( this.renderPanel( f.section ) );
}
} else if ( f.section == section ) {
els.push( this.fieldEl( f, func, i ) );
}
}
} else if ( !f['type'].includes('Toolbar') ) {
console.error( `${functionSuffix.replace( 'Init', '' )} ${f['id']} of type ${f['type']} and callback ${func} not supported.` );
}
}
return els;
}
toolbarElements() {
const els = this.renderFields( this.toolbars, false, 'ToolbarInit' );
if ( els.length ) {
return el(
editor.BlockControls,
{ key: 'toolbars' },
els
);
}
}
inspectorFields() {
const fields = this.fields;
const panelProps = {};
let panelFields;
let els = [];
const th = this;
els = els.concat( th.renderFields( fields ) );
if ( els && els.length ) {
return el(
editor.InspectorControls,
{ key: 'inspector' },
els
);
}
}
// region Register block
populateFields(html, edit) {
if ( ! html ) {
return '';
}
let c2e;
let tag;
for ( let f in this.fields ) {
if ( this.fields.hasOwnProperty( f ) ) {
let _val;
const fld = this.fields[ f ];
let val = _val = this.attrs[fld.id];
if ( fld.type === 'editable' ) {
tag = fld.tag ? fld.tag : 'span';
if ( edit ) {
if ( val === fld.default ) {
val = `<${tag} class="default">${val}${tag}>`;
}
c2e = __( 'Click to Edit' );
val =
`<${tag} contentEditable="true" title="${c2e}" data-editableproperty="${fld.id}">${val}${tag}>`;
} else {
if ( val ) {
val = `<${tag}>${val}${tag}>`;
}
}
}
if ( ( val || typeof val === 'number' ) && fld.tpl ) {
val = fld.tpl.replace( /%s/g, val );
}
html = html.split( `{{_${fld.id}}}` ).join( _val );
html = html.split( `{{${fld.id}}}` ).join( val );
}
}
return html;
}
outputHTML(html, edit) {
html = this.populateFields( html, edit );
html = this.populateFields( html, edit ); // Twice to allow using dynamic fields in
return { __html: html };
}
getBlockTpl( props ) {
if ( typeof this.tpl === 'function' ) {
return this.tpl( props, this );
} else {
return this.tpl;
}
}
edit(props) {
let that = this;
if ( that.block ) {
if ( typeof that.block.edit === 'function' ) {
return that.block.edit( props, that );
}
return el( 'div', {
key: 'block',
dangerouslySetInnerHTML: that.outputHTML( that.getBlockTpl( props ), 'edit' ),
onClick(e) {
e.preventDefault();
},
onKeyDown({target}) {
const $def = $( target ).find( '.default' );
if ( $def.length ) $def.remove();
},
onBlur({target}) {
const $t = $( target );
const attrs = {};
const prop = $t.data( 'editableproperty' );
attrs[prop] = $t.html();
console.log( attrs, prop );
that.focussedProps.setAttributes( attrs );
},
} );
}
}
save(props) {
const id = this.block.id;
if ( this.block ) {
if ( typeof this.block.save === 'function' ) {
return this.block.save( props, this );
}
return el( 'div', {dangerouslySetInnerHTML: this.outputHTML( this.getBlockTpl( props ) )} );
}
}
saveBlockProperties( props ) {
this.props = props;
this.attrs = this.props.attributes;
for ( let f in this.fields ) {
if ( this.fields.hasOwnProperty( f ) ) {
const fld = this.fields[ f ];
if ( ! this.attrs[fld.id] && isNaN( this.attrs[fld.id] ) ) {
this.attrs[fld.id] = fld.default
}
}
}
}
registerBlock() {
let attrName;
const that = this;
const block = this.block;
const registerBlockProps = $.extend( {}, block );
if ( block.icon.includes('