diff --git a/extensions/README.md b/extensions/README.md index 91375f3469d..b7de2b67563 100644 --- a/extensions/README.md +++ b/extensions/README.md @@ -233,3 +233,9 @@ The build pipeline also supports [Color studio](https://github.com/Automattic/co ### Icons Please use outline versions of [Material icons](https://material.io/tools/icons/?style=outline) to stay in line with Gutenberg. Don't rely on icons used in WordPress core to avoid visual mixing up with core blocks. + +## Native support + +This is still very much experimental and subject to change. +React Native support for Jetpack blocks is being added as part of the WordPress [Android](https://github.com/wordpress-mobile/WordPress-Android) and [iOS](https://github.com/wordpress-mobile/WordPress-iOS) apps. +A react-native build configuration will attempt to resolve `.native.js` extensions before `.js` ones, making `.native.js` a simple approach to write "cross-platform" gutenberg blocks. diff --git a/extensions/blocks/contact-info/address/edit.native.js b/extensions/blocks/contact-info/address/edit.native.js new file mode 100644 index 00000000000..dc9ccd3c5c1 --- /dev/null +++ b/extensions/blocks/contact-info/address/edit.native.js @@ -0,0 +1,117 @@ +/** + * External dependencies + */ +import { View } from 'react-native'; +import { __ } from '@wordpress/i18n'; +import { Component, Fragment } from '@wordpress/element'; +import { PlainText } from '@wordpress/block-editor'; +import { ToggleControl } from '@wordpress/components'; + +class AddressEdit extends Component { + constructor( ...args ) { + super( ...args ); + + this.preventEnterKey = this.preventEnterKey.bind( this ); + } + + preventEnterKey( event ) { + if ( event.key === 'Enter' ) { + event.preventDefault(); + return; + } + } + + render() { + const { + attributes: { + address, + addressLine2, + addressLine3, + city, + region, + postal, + country, + linkToGoogleMaps, + }, + setAttributes, + onFocus, + } = this.props; + + const externalLink = ( + { + setAttributes( { linkToGoogleMaps: newlinkToGoogleMaps } ); + onFocus(); + } } + /> + ); + + return ( + + + setAttributes( { address: newAddress } ) } + onKeyDown={ this.preventEnterKey } + onFocus={ onFocus } + /> + <PlainText + value={ addressLine2 } + placeholder={ __( 'Address Line 2', 'jetpack' ) } + aria-label={ __( 'Address Line 2', 'jetpack' ) } + onChange={ newAddressLine2 => setAttributes( { addressLine2: newAddressLine2 } ) } + onKeyDown={ this.preventEnterKey } + onFocus={ onFocus } + /> + <PlainText + value={ addressLine3 } + placeholder={ __( 'Address Line 3', 'jetpack' ) } + aria-label={ __( 'Address Line 3', 'jetpack' ) } + onChange={ newAddressLine3 => setAttributes( { addressLine3: newAddressLine3 } ) } + onKeyDown={ this.preventEnterKey } + onFocus={ onFocus } + /> + <PlainText + value={ city } + placeholder={ __( 'City', 'jetpack' ) } + aria-label={ __( 'City', 'jetpack' ) } + onChange={ newCity => setAttributes( { city: newCity } ) } + onKeyDown={ this.preventEnterKey } + onFocus={ onFocus } + /> + <PlainText + value={ region } + placeholder={ __( 'State/Province/Region', 'jetpack' ) } + aria-label={ __( 'State/Province/Region', 'jetpack' ) } + onChange={ newRegion => setAttributes( { region: newRegion } ) } + onKeyDown={ this.preventEnterKey } + onFocus={ onFocus } + /> + <PlainText + value={ postal } + placeholder={ __( 'Postal/Zip Code', 'jetpack' ) } + aria-label={ __( 'Postal/Zip Code', 'jetpack' ) } + onChange={ newPostal => setAttributes( { postal: newPostal } ) } + onKeyDown={ this.preventEnterKey } + onFocus={ onFocus } + /> + <PlainText + value={ country } + placeholder={ __( 'Country', 'jetpack' ) } + aria-label={ __( 'Country', 'jetpack' ) } + onChange={ newCountry => setAttributes( { country: newCountry } ) } + onKeyDown={ this.preventEnterKey } + onFocus={ onFocus } + /> + { externalLink } + </Fragment> + </View> + ); + } +} + +export default AddressEdit; diff --git a/extensions/blocks/contact-info/edit.native.js b/extensions/blocks/contact-info/edit.native.js new file mode 100644 index 00000000000..f71edf5c300 --- /dev/null +++ b/extensions/blocks/contact-info/edit.native.js @@ -0,0 +1,41 @@ +/** + * External dependencies + */ +import { InnerBlocks } from '@wordpress/block-editor'; +import { View } from 'react-native'; + +/** + * Internal dependencies + */ +const ALLOWED_BLOCKS = [ + 'jetpack/address', + 'jetpack/email', + 'jetpack/phone', + 'core/paragraph', + 'core/image', + 'core/heading', + 'core/gallery', + 'core/list', + 'core/quote', + 'core/shortcode', + 'core/audio', + 'core/code', + 'core/cover', + 'core/html', + 'core/separator', + 'core/spacer', + 'core/subhead', + 'core/video', +]; + +const TEMPLATE = [ [ 'jetpack/email' ], [ 'jetpack/phone' ], [ 'jetpack/address' ] ]; + +const ContactInfoEdit = () => { + return ( + <View> + <InnerBlocks allowedBlocks={ ALLOWED_BLOCKS } templateLock={ false } template={ TEMPLATE } /> + </View> + ); +}; + +export default ContactInfoEdit; diff --git a/extensions/editor.native.js b/extensions/editor.native.js new file mode 100644 index 00000000000..cb3628e4bbb --- /dev/null +++ b/extensions/editor.native.js @@ -0,0 +1,7 @@ +/** + * Internal dependencies + */ +import './shared/block-category'; + +// Register blocks +import './blocks/contact-info/editor'; diff --git a/extensions/shared/register-jetpack-block.native.js b/extensions/shared/register-jetpack-block.native.js new file mode 100644 index 00000000000..ed285e98088 --- /dev/null +++ b/extensions/shared/register-jetpack-block.native.js @@ -0,0 +1,63 @@ +/** + * External dependencies + */ +import { registerBlockType } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import extensionList from '../index.json'; +import getJetpackExtensionAvailability from './get-jetpack-extension-availability'; + +const betaExtensions = extensionList.beta || []; + +function requiresPlan( unavailableReason, details ) { + if ( unavailableReason === 'missing_plan' ) { + return details.required_plan; + } + return false; +} + +/** + * Registers a gutenberg block if the availability requirements are met. + * + * @param {string} name The block's name. + * @param {object} settings The block's settings. + * @param {object} childBlocks The block's child blocks. + * @returns {object|false} Either false if the block is not available, or the results of `registerBlockType` + */ +export default function registerJetpackBlock( name, settings, childBlocks = [] ) { + const { available, details, unavailableReason } = getJetpackExtensionAvailability( name ); + + const requiredPlan = requiresPlan( unavailableReason, details ); + + if ( ! available && ! requiredPlan ) { + if ( 'production' !== process.env.NODE_ENV ) { + // eslint-disable-next-line no-console + console.warn( + `Block ${ name } couldn't be registered because it is unavailable (${ unavailableReason }).` + ); + } + return false; + } + + const result = registerBlockType( `jetpack/${ name }`, { + ...settings, + title: betaExtensions.includes( name ) ? `${ settings.title } (beta)` : settings.title, + edit: settings.edit, + example: requiredPlan ? undefined : settings.example, + } ); + + if ( 'production' !== process.env.NODE_ENV ) { + // eslint-disable-next-line no-console + console.log( `Block jetpack/${ name } registered.` ); + } + + // Register child blocks. Using `registerBlockType()` directly avoids availability checks -- if + // their parent is available, we register them all, without checking for their individual availability. + childBlocks.forEach( childBlock => + registerBlockType( `jetpack/${ childBlock.name }`, childBlock.settings ) + ); + + return result; +} diff --git a/extensions/shared/simple-input.native.js b/extensions/shared/simple-input.native.js new file mode 100644 index 00000000000..a90066b9dd3 --- /dev/null +++ b/extensions/shared/simple-input.native.js @@ -0,0 +1,23 @@ +/** + * External dependencies + */ +import { PlainText } from '@wordpress/block-editor'; +import { View } from 'react-native'; + +const simpleInput = ( type, props, label, view, onChange ) => { + const { onFocus } = props; + const value = props.attributes[ type ]; + return ( + <View> + <PlainText + value={ value } + placeholder={ label } + aria-label={ label } + onChange={ onChange } + onFocus={ onFocus } + /> + </View> + ); +}; + +export default simpleInput;