summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/jetpack/extensions/blocks/contact-form/components')
-rw-r--r--plugins/jetpack/extensions/blocks/contact-form/components/jetpack-contact-form.js266
-rw-r--r--plugins/jetpack/extensions/blocks/contact-form/components/jetpack-field-checkbox.js62
-rw-r--r--plugins/jetpack/extensions/blocks/contact-form/components/jetpack-field-label.js35
-rw-r--r--plugins/jetpack/extensions/blocks/contact-form/components/jetpack-field-multiple.js122
-rw-r--r--plugins/jetpack/extensions/blocks/contact-form/components/jetpack-field-textarea.js59
-rw-r--r--plugins/jetpack/extensions/blocks/contact-form/components/jetpack-field.js62
-rw-r--r--plugins/jetpack/extensions/blocks/contact-form/components/jetpack-option.js80
7 files changed, 686 insertions, 0 deletions
diff --git a/plugins/jetpack/extensions/blocks/contact-form/components/jetpack-contact-form.js b/plugins/jetpack/extensions/blocks/contact-form/components/jetpack-contact-form.js
new file mode 100644
index 00000000..952a3934
--- /dev/null
+++ b/plugins/jetpack/extensions/blocks/contact-form/components/jetpack-contact-form.js
@@ -0,0 +1,266 @@
+/**
+ * External dependencies
+ */
+import classnames from 'classnames';
+import emailValidator from 'email-validator';
+import { __, sprintf } from '@wordpress/i18n';
+import { Button, PanelBody, Path, Placeholder, TextControl } from '@wordpress/components';
+import { Component, Fragment } from '@wordpress/element';
+import { compose, withInstanceId } from '@wordpress/compose';
+import { InnerBlocks, InspectorControls } from '@wordpress/editor';
+
+/**
+ * Internal dependencies
+ */
+import HelpMessage from '../../../shared/help-message';
+import renderMaterialIcon from '../../../shared/render-material-icon';
+import SubmitButton from '../../../shared/submit-button';
+
+const ALLOWED_BLOCKS = [
+ 'jetpack/markdown',
+ 'core/paragraph',
+ 'core/image',
+ 'core/heading',
+ 'core/gallery',
+ 'core/list',
+ 'core/quote',
+ 'core/shortcode',
+ 'core/audio',
+ 'core/code',
+ 'core/cover',
+ 'core/file',
+ 'core/html',
+ 'core/separator',
+ 'core/spacer',
+ 'core/subhead',
+ 'core/table',
+ 'core/verse',
+ 'core/video',
+];
+
+class JetpackContactForm extends Component {
+ constructor( ...args ) {
+ super( ...args );
+ this.onChangeSubject = this.onChangeSubject.bind( this );
+ this.onBlurTo = this.onBlurTo.bind( this );
+ this.onChangeTo = this.onChangeTo.bind( this );
+ this.onChangeSubmit = this.onChangeSubmit.bind( this );
+ this.onFormSettingsSet = this.onFormSettingsSet.bind( this );
+ this.getToValidationError = this.getToValidationError.bind( this );
+ this.renderToAndSubjectFields = this.renderToAndSubjectFields.bind( this );
+ this.preventEnterSubmittion = this.preventEnterSubmittion.bind( this );
+ this.hasEmailError = this.hasEmailError.bind( this );
+
+ const to = args[ 0 ].attributes.to ? args[ 0 ].attributes.to : '';
+ const error = to
+ .split( ',' )
+ .map( this.getToValidationError )
+ .filter( Boolean );
+
+ this.state = {
+ toError: error && error.length ? error : null,
+ };
+ }
+
+ getIntroMessage() {
+ return __(
+ 'You’ll receive an email notification each time someone fills out the form. Where should it go, and what should the subject line be?',
+ 'jetpack'
+ );
+ }
+
+ getEmailHelpMessage() {
+ return __( 'You can enter multiple email addresses separated by commas.', 'jetpack' );
+ }
+
+ onChangeSubject( subject ) {
+ this.props.setAttributes( { subject } );
+ }
+
+ getToValidationError( email ) {
+ email = email.trim();
+ if ( email.length === 0 ) {
+ return false; // ignore the empty emails
+ }
+ if ( ! emailValidator.validate( email ) ) {
+ return { email };
+ }
+ return false;
+ }
+
+ onBlurTo( event ) {
+ const error = event.target.value
+ .split( ',' )
+ .map( this.getToValidationError )
+ .filter( Boolean );
+ if ( error && error.length ) {
+ this.setState( { toError: error } );
+ return;
+ }
+ }
+
+ onChangeTo( to ) {
+ const emails = to.trim();
+ if ( emails.length === 0 ) {
+ this.setState( { toError: null } );
+ this.props.setAttributes( { to } );
+ return;
+ }
+
+ this.setState( { toError: null } );
+ this.props.setAttributes( { to } );
+ }
+
+ onChangeSubmit( submitButtonText ) {
+ this.props.setAttributes( { submitButtonText } );
+ }
+
+ onFormSettingsSet( event ) {
+ event.preventDefault();
+ if ( this.state.toError ) {
+ // don't submit the form if there are errors.
+ return;
+ }
+ this.props.setAttributes( { hasFormSettingsSet: 'yes' } );
+ }
+
+ getfieldEmailError( errors ) {
+ if ( errors ) {
+ if ( errors.length === 1 ) {
+ if ( errors[ 0 ] && errors[ 0 ].email ) {
+ return sprintf( __( '%s is not a valid email address.', 'jetpack' ), errors[ 0 ].email );
+ }
+ return errors[ 0 ];
+ }
+
+ if ( errors.length === 2 ) {
+ return sprintf(
+ __( '%s and %s are not a valid email address.', 'jetpack' ),
+ errors[ 0 ].email,
+ errors[ 1 ].email
+ );
+ }
+ const inValidEmails = errors.map( error => error.email );
+ return sprintf(
+ __( '%s are not a valid email address.', 'jetpack' ),
+ inValidEmails.join( ', ' )
+ );
+ }
+ return null;
+ }
+
+ preventEnterSubmittion( event ) {
+ if ( event.key === 'Enter' ) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+
+ renderToAndSubjectFields() {
+ const fieldEmailError = this.state.toError;
+ const { instanceId, attributes } = this.props;
+ const { subject, to } = attributes;
+ return (
+ <Fragment>
+ <TextControl
+ aria-describedby={ `contact-form-${ instanceId }-email-${
+ this.hasEmailError() ? 'error' : 'help'
+ }` }
+ label={ __( 'Email address', 'jetpack' ) }
+ placeholder={ __( 'name@example.com', 'jetpack' ) }
+ onKeyDown={ this.preventEnterSubmittion }
+ value={ to }
+ onBlur={ this.onBlurTo }
+ onChange={ this.onChangeTo }
+ />
+ <HelpMessage isError id={ `contact-form-${ instanceId }-email-error` }>
+ { this.getfieldEmailError( fieldEmailError ) }
+ </HelpMessage>
+ <HelpMessage id={ `contact-form-${ instanceId }-email-help` }>
+ { this.getEmailHelpMessage() }
+ </HelpMessage>
+
+ <TextControl
+ label={ __( 'Email subject line', 'jetpack' ) }
+ value={ subject }
+ placeholder={ __( "Let's work together", 'jetpack' ) }
+ onChange={ this.onChangeSubject }
+ />
+ </Fragment>
+ );
+ }
+
+ hasEmailError() {
+ const fieldEmailError = this.state.toError;
+ return fieldEmailError && fieldEmailError.length > 0;
+ }
+
+ render() {
+ const { className, attributes } = this.props;
+ const { hasFormSettingsSet } = attributes;
+ const formClassnames = classnames( className, 'jetpack-contact-form', {
+ 'has-intro': ! hasFormSettingsSet,
+ } );
+
+ return (
+ <Fragment>
+ <InspectorControls>
+ <PanelBody title={ __( 'Email feedback settings', 'jetpack' ) }>
+ { this.renderToAndSubjectFields() }
+ </PanelBody>
+ </InspectorControls>
+ <div className={ formClassnames }>
+ { ! hasFormSettingsSet && (
+ <Placeholder
+ label={ __( 'Form', 'jetpack' ) }
+ icon={ renderMaterialIcon(
+ <Path d="M13 7.5h5v2h-5zm0 7h5v2h-5zM19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V5h14v14zM11 6H6v5h5V6zm-1 4H7V7h3v3zm1 3H6v5h5v-5zm-1 4H7v-3h3v3z" />
+ ) }
+ >
+ <form onSubmit={ this.onFormSettingsSet }>
+ <p className="jetpack-contact-form__intro-message">{ this.getIntroMessage() }</p>
+ { this.renderToAndSubjectFields() }
+ <p className="jetpack-contact-form__intro-message">
+ { __(
+ '(If you leave these blank, notifications will go to the author with the post or page title as the subject line.)',
+ 'jetpack'
+ ) }
+ </p>
+ <div className="jetpack-contact-form__create">
+ <Button isPrimary type="submit" disabled={ this.hasEmailError() }>
+ { __( 'Add form', 'jetpack' ) }
+ </Button>
+ </div>
+ </form>
+ </Placeholder>
+ ) }
+ { hasFormSettingsSet && (
+ <InnerBlocks
+ allowedBlocks={ ALLOWED_BLOCKS }
+ templateLock={ false }
+ template={ [
+ [
+ 'jetpack/field-name',
+ {
+ required: true,
+ },
+ ],
+ [
+ 'jetpack/field-email',
+ {
+ required: true,
+ },
+ ],
+ [ 'jetpack/field-url', {} ],
+ [ 'jetpack/field-textarea', {} ],
+ ] }
+ />
+ ) }
+ { hasFormSettingsSet && <SubmitButton { ...this.props } /> }
+ </div>
+ </Fragment>
+ );
+ }
+}
+
+export default compose( [ withInstanceId ] )( JetpackContactForm );
diff --git a/plugins/jetpack/extensions/blocks/contact-form/components/jetpack-field-checkbox.js b/plugins/jetpack/extensions/blocks/contact-form/components/jetpack-field-checkbox.js
new file mode 100644
index 00000000..05e49dc4
--- /dev/null
+++ b/plugins/jetpack/extensions/blocks/contact-form/components/jetpack-field-checkbox.js
@@ -0,0 +1,62 @@
+/**
+ * External dependencies
+ */
+import { __ } from '@wordpress/i18n';
+import { BaseControl, PanelBody, TextControl, ToggleControl } from '@wordpress/components';
+import { Fragment } from '@wordpress/element';
+import { InspectorControls } from '@wordpress/editor';
+import { withInstanceId } from '@wordpress/compose';
+
+/**
+ * Internal dependencies
+ */
+import JetpackFieldLabel from './jetpack-field-label';
+
+const JetpackFieldCheckbox = ( {
+ instanceId,
+ required,
+ label,
+ setAttributes,
+ isSelected,
+ defaultValue,
+ id,
+} ) => {
+ return (
+ <BaseControl
+ id={ `jetpack-field-checkbox-${ instanceId }` }
+ className="jetpack-field jetpack-field-checkbox"
+ label={
+ <Fragment>
+ <input
+ className="jetpack-field-checkbox__checkbox"
+ type="checkbox"
+ disabled
+ checked={ defaultValue }
+ />
+ <JetpackFieldLabel
+ required={ required }
+ label={ label }
+ setAttributes={ setAttributes }
+ isSelected={ isSelected }
+ />
+ <InspectorControls>
+ <PanelBody title={ __( 'Field Settings', 'jetpack' ) }>
+ <ToggleControl
+ label={ __( 'Default Checked State', 'jetpack' ) }
+ checked={ defaultValue }
+ onChange={ value => setAttributes( { defaultValue: value } ) }
+ />
+ <TextControl
+ label={ __( 'ID', 'jetpack' ) }
+ value={ id }
+ onChange={ value => setAttributes( { id: value } ) }
+ />
+ </PanelBody>
+ </InspectorControls>
+ </Fragment>
+ }
+ />
+ );
+};
+
+export default withInstanceId( JetpackFieldCheckbox );
diff --git a/plugins/jetpack/extensions/blocks/contact-form/components/jetpack-field-label.js b/plugins/jetpack/extensions/blocks/contact-form/components/jetpack-field-label.js
new file mode 100644
index 00000000..0ee3d7ba
--- /dev/null
+++ b/plugins/jetpack/extensions/blocks/contact-form/components/jetpack-field-label.js
@@ -0,0 +1,35 @@
+/**
+ * External dependencies
+ */
+import { __ } from '@wordpress/i18n';
+import { PlainText } from '@wordpress/editor';
+import { ToggleControl } from '@wordpress/components';
+
+const JetpackFieldLabel = ( { setAttributes, label, resetFocus, isSelected, required } ) => {
+ return (
+ <div className="jetpack-field-label">
+ <PlainText
+ value={ label }
+ className="jetpack-field-label__input"
+ onChange={ value => {
+ resetFocus && resetFocus();
+ setAttributes( { label: value } );
+ } }
+ placeholder={ __( 'Write label…', 'jetpack' ) }
+ />
+ { isSelected && (
+ <ToggleControl
+ label={ __( 'Required', 'jetpack' ) }
+ className="jetpack-field-label__required"
+ checked={ required }
+ onChange={ value => setAttributes( { required: value } ) }
+ />
+ ) }
+ { ! isSelected && required && (
+ <span className="required">{ __( '(required)', 'jetpack' ) }</span>
+ ) }
+ </div>
+ );
+};
+
+export default JetpackFieldLabel;
diff --git a/plugins/jetpack/extensions/blocks/contact-form/components/jetpack-field-multiple.js b/plugins/jetpack/extensions/blocks/contact-form/components/jetpack-field-multiple.js
new file mode 100644
index 00000000..292bdeaf
--- /dev/null
+++ b/plugins/jetpack/extensions/blocks/contact-form/components/jetpack-field-multiple.js
@@ -0,0 +1,122 @@
+/**
+ * External dependencies
+ */
+import { __ } from '@wordpress/i18n';
+import { BaseControl, IconButton, PanelBody, TextControl } from '@wordpress/components';
+import { Component, Fragment } from '@wordpress/element';
+import { InspectorControls } from '@wordpress/editor';
+import { withInstanceId } from '@wordpress/compose';
+
+/**
+ * Internal dependencies
+ */
+import JetpackFieldLabel from './jetpack-field-label';
+import JetpackOption from './jetpack-option';
+
+class JetpackFieldMultiple extends Component {
+ constructor( ...args ) {
+ super( ...args );
+ this.onChangeOption = this.onChangeOption.bind( this );
+ this.addNewOption = this.addNewOption.bind( this );
+ this.state = { inFocus: null };
+ }
+
+ onChangeOption( key = null, option = null ) {
+ const newOptions = this.props.options.slice( 0 );
+ if ( null === option ) {
+ // Remove a key
+ newOptions.splice( key, 1 );
+ if ( key > 0 ) {
+ this.setState( { inFocus: key - 1 } );
+ }
+ } else {
+ // update a key
+ newOptions.splice( key, 1, option );
+ this.setState( { inFocus: key } ); // set the focus.
+ }
+ this.props.setAttributes( { options: newOptions } );
+ }
+
+ addNewOption( key = null ) {
+ const newOptions = this.props.options.slice( 0 );
+ let inFocus = 0;
+ if ( 'object' === typeof key ) {
+ newOptions.push( '' );
+ inFocus = newOptions.length - 1;
+ } else {
+ newOptions.splice( key + 1, 0, '' );
+ inFocus = key + 1;
+ }
+
+ this.setState( { inFocus: inFocus } );
+ this.props.setAttributes( { options: newOptions } );
+ }
+
+ render() {
+ const { type, instanceId, required, label, setAttributes, isSelected, id } = this.props;
+ let { options } = this.props;
+ let { inFocus } = this.state;
+ if ( ! options.length ) {
+ options = [ '' ];
+ inFocus = 0;
+ }
+
+ return (
+ <Fragment>
+ <BaseControl
+ id={ `jetpack-field-multiple-${ instanceId }` }
+ className="jetpack-field jetpack-field-multiple"
+ label={
+ <JetpackFieldLabel
+ required={ required }
+ label={ label }
+ setAttributes={ setAttributes }
+ isSelected={ isSelected }
+ resetFocus={ () => this.setState( { inFocus: null } ) }
+ />
+ }
+ >
+ <ol
+ className="jetpack-field-multiple__list"
+ id={ `jetpack-field-multiple-${ instanceId }` }
+ >
+ { options.map( ( option, index ) => (
+ <JetpackOption
+ type={ type }
+ key={ index }
+ option={ option }
+ index={ index }
+ onChangeOption={ this.onChangeOption }
+ onAddOption={ this.addNewOption }
+ isInFocus={ index === inFocus && isSelected }
+ isSelected={ isSelected }
+ />
+ ) ) }
+ </ol>
+ { isSelected && (
+ <IconButton
+ className="jetpack-field-multiple__add-option"
+ icon="insert"
+ label={ __( 'Insert option', 'jetpack' ) }
+ onClick={ this.addNewOption }
+ >
+ { __( 'Add option', 'jetpack' ) }
+ </IconButton>
+ ) }
+ </BaseControl>
+
+ <InspectorControls>
+ <PanelBody title={ __( 'Field Settings', 'jetpack' ) }>
+ <TextControl
+ label={ __( 'ID', 'jetpack' ) }
+ value={ id }
+ onChange={ value => setAttributes( { id: value } ) }
+ />
+ </PanelBody>
+ </InspectorControls>
+ </Fragment>
+ );
+ }
+}
+
+export default withInstanceId( JetpackFieldMultiple );
diff --git a/plugins/jetpack/extensions/blocks/contact-form/components/jetpack-field-textarea.js b/plugins/jetpack/extensions/blocks/contact-form/components/jetpack-field-textarea.js
new file mode 100644
index 00000000..e2025941
--- /dev/null
+++ b/plugins/jetpack/extensions/blocks/contact-form/components/jetpack-field-textarea.js
@@ -0,0 +1,59 @@
+/**
+ * External dependencies
+ */
+import { __ } from '@wordpress/i18n';
+import { Fragment } from '@wordpress/element';
+import { InspectorControls } from '@wordpress/editor';
+import { PanelBody, TextareaControl, TextControl } from '@wordpress/components';
+
+/**
+ * Internal dependencies
+ */
+import JetpackFieldLabel from './jetpack-field-label';
+
+function JetpackFieldTextarea( {
+ required,
+ label,
+ setAttributes,
+ isSelected,
+ defaultValue,
+ placeholder,
+ id,
+} ) {
+ return (
+ <Fragment>
+ <div className="jetpack-field">
+ <TextareaControl
+ label={
+ <JetpackFieldLabel
+ required={ required }
+ label={ label }
+ setAttributes={ setAttributes }
+ isSelected={ isSelected }
+ />
+ }
+ placeholder={ placeholder }
+ value={ placeholder }
+ onChange={ value => setAttributes( { placeholder: value } ) }
+ title={ __( 'Set the placeholder text', 'jetpack' ) }
+ />
+ </div>
+ <InspectorControls>
+ <PanelBody title={ __( 'Field Settings', 'jetpack' ) }>
+ <TextControl
+ label={ __( 'Default Value', 'jetpack' ) }
+ value={ defaultValue }
+ onChange={ value => setAttributes( { defaultValue: value } ) }
+ />
+ <TextControl
+ label={ __( 'ID', 'jetpack' ) }
+ value={ id }
+ onChange={ value => setAttributes( { id: value } ) }
+ />
+ </PanelBody>
+ </InspectorControls>
+ </Fragment>
+ );
+}
+
+export default JetpackFieldTextarea;
diff --git a/plugins/jetpack/extensions/blocks/contact-form/components/jetpack-field.js b/plugins/jetpack/extensions/blocks/contact-form/components/jetpack-field.js
new file mode 100644
index 00000000..6a8269ff
--- /dev/null
+++ b/plugins/jetpack/extensions/blocks/contact-form/components/jetpack-field.js
@@ -0,0 +1,62 @@
+/**
+ * External dependencies
+ */
+import classNames from 'classnames';
+import { __ } from '@wordpress/i18n';
+import { Fragment } from '@wordpress/element';
+import { InspectorControls } from '@wordpress/editor';
+import { PanelBody, TextControl } from '@wordpress/components';
+
+/**
+ * Internal dependencies
+ */
+import JetpackFieldLabel from './jetpack-field-label';
+
+function JetpackField( {
+ isSelected,
+ type,
+ required,
+ label,
+ setAttributes,
+ defaultValue,
+ placeholder,
+ id,
+} ) {
+ return (
+ <Fragment>
+ <div className={ classNames( 'jetpack-field', { 'is-selected': isSelected } ) }>
+ <TextControl
+ type={ type }
+ label={
+ <JetpackFieldLabel
+ required={ required }
+ label={ label }
+ setAttributes={ setAttributes }
+ isSelected={ isSelected }
+ />
+ }
+ placeholder={ placeholder }
+ value={ placeholder }
+ onChange={ value => setAttributes( { placeholder: value } ) }
+ title={ __( 'Set the placeholder text', 'jetpack' ) }
+ />
+ </div>
+ <InspectorControls>
+ <PanelBody title={ __( 'Field Settings', 'jetpack' ) }>
+ <TextControl
+ label={ __( 'Default Value', 'jetpack' ) }
+ value={ defaultValue }
+ onChange={ value => setAttributes( { defaultValue: value } ) }
+ />
+ <TextControl
+ label={ __( 'ID', 'jetpack' ) }
+ value={ id }
+ onChange={ value => setAttributes( { id: value } ) }
+ />
+ </PanelBody>
+ </InspectorControls>
+ </Fragment>
+ );
+}
+
+export default JetpackField;
diff --git a/plugins/jetpack/extensions/blocks/contact-form/components/jetpack-option.js b/plugins/jetpack/extensions/blocks/contact-form/components/jetpack-option.js
new file mode 100644
index 00000000..8cd2792d
--- /dev/null
+++ b/plugins/jetpack/extensions/blocks/contact-form/components/jetpack-option.js
@@ -0,0 +1,80 @@
+/**
+ * External dependencies
+ */
+import { __ } from '@wordpress/i18n';
+import { IconButton } from '@wordpress/components';
+import { Component, createRef } from '@wordpress/element';
+
+class JetpackOption extends Component {
+ constructor( ...args ) {
+ super( ...args );
+ this.onChangeOption = this.onChangeOption.bind( this );
+ this.onKeyPress = this.onKeyPress.bind( this );
+ this.onDeleteOption = this.onDeleteOption.bind( this );
+ this.textInput = createRef();
+ }
+
+ componentDidMount() {
+ if ( this.props.isInFocus ) {
+ this.textInput.current.focus();
+ }
+ }
+
+ componentDidUpdate() {
+ if ( this.props.isInFocus ) {
+ this.textInput.current.focus();
+ }
+ }
+
+ onChangeOption( event ) {
+ this.props.onChangeOption( this.props.index, event.target.value );
+ }
+
+ onKeyPress( event ) {
+ if ( event.key === 'Enter' ) {
+ this.props.onAddOption( this.props.index );
+ event.preventDefault();
+ return;
+ }
+
+ if ( event.key === 'Backspace' && event.target.value === '' ) {
+ this.props.onChangeOption( this.props.index );
+ event.preventDefault();
+ return;
+ }
+ }
+
+ onDeleteOption() {
+ this.props.onChangeOption( this.props.index );
+ }
+
+ render() {
+ const { isSelected, option, type } = this.props;
+ return (
+ <li className="jetpack-option">
+ { type && type !== 'select' && (
+ <input className="jetpack-option__type" type={ type } disabled />
+ ) }
+ <input
+ type="text"
+ className="jetpack-option__input"
+ value={ option }
+ placeholder={ __( 'Write option…', 'jetpack' ) }
+ onChange={ this.onChangeOption }
+ onKeyDown={ this.onKeyPress }
+ ref={ this.textInput }
+ />
+ { isSelected && (
+ <IconButton
+ className="jetpack-option__remove"
+ icon="trash"
+ label={ __( 'Remove option', 'jetpack' ) }
+ onClick={ this.onDeleteOption }
+ />
+ ) }
+ </li>
+ );
+ }
+}
+
+export default JetpackOption;