diff --git a/client/gutenberg/extensions/contact-info/address/edit.js b/client/gutenberg/extensions/contact-info/address/edit.js new file mode 100644 index 00000000000000..d1223898c6e8ef --- /dev/null +++ b/client/gutenberg/extensions/contact-info/address/edit.js @@ -0,0 +1,129 @@ +/** @format */ + +/** + * External dependencies + */ +import classnames from 'classnames'; +import { PlainText, InspectorControls } from '@wordpress/editor'; +import { Component, Fragment } from '@wordpress/element'; +import { ToggleControl, PanelBody, ExternalLink } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import { __ } from 'gutenberg/extensions/presets/jetpack/utils/i18n'; +import ClipboardInput from 'gutenberg/extensions/presets/jetpack/utils/clipboard-input'; +import { default as save, googleMapsUrl } from './save'; + +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, + }, + isSelected, + setAttributes, + } = this.props; + + const hasContent = [ address, addressLine2, addressLine3, city, region, postal, country ].some( + value => value !== '' + ); + const classNames = classnames( { + 'jetpack-address-block': true, + 'is-selected': isSelected, + } ); + + return ( +
+ { ! isSelected && hasContent && save( this.props ) } + { ( isSelected || ! hasContent ) && ( + + setAttributes( { address: newAddress } ) } + onKeyDown={ this.preventEnterKey } + /> + <PlainText + value={ addressLine2 } + placeholder={ __( 'Address Line 2' ) } + onChange={ newAddressLine2 => setAttributes( { addressLine2: newAddressLine2 } ) } + onKeyDown={ this.preventEnterKey } + /> + <PlainText + value={ addressLine3 } + placeholder={ __( 'Address Line 3' ) } + onChange={ newAddressLine3 => setAttributes( { addressLine3: newAddressLine3 } ) } + onKeyDown={ this.preventEnterKey } + /> + <PlainText + value={ city } + placeholder={ __( 'City' ) } + onChange={ newCity => setAttributes( { city: newCity } ) } + onKeyDown={ this.preventEnterKey } + /> + <PlainText + value={ region } + placeholder={ __( 'State/Province/Region' ) } + onChange={ newRegion => setAttributes( { region: newRegion } ) } + onKeyDown={ this.preventEnterKey } + /> + <PlainText + value={ postal } + placeholder={ __( 'Postal/Zip Code' ) } + onChange={ newPostal => setAttributes( { postal: newPostal } ) } + onKeyDown={ this.preventEnterKey } + /> + <PlainText + value={ country } + placeholder={ __( 'Country' ) } + onChange={ newCountry => setAttributes( { country: newCountry } ) } + onKeyDown={ this.preventEnterKey } + /> + <InspectorControls> + <PanelBody title={ __( 'Link to Google Maps' ) }> + <ToggleControl + label={ __( 'Link address to Google Maps' ) } + checked={ linkToGoogleMaps } + onChange={ newlinkToGoogleMaps => + setAttributes( { linkToGoogleMaps: newlinkToGoogleMaps } ) + } + /> + { hasContent && <ClipboardInput link={ googleMapsUrl( this.props ) } /> } + { hasContent && ( + <div> + <ExternalLink href={ googleMapsUrl( this.props ) }> + { __( 'Visit Google Maps' ) } + </ExternalLink> + </div> + ) } + </PanelBody> + </InspectorControls> + </Fragment> + ) } + </div> + ); + } +} + +export default AddressEdit; diff --git a/client/gutenberg/extensions/contact-info/address/editor.js b/client/gutenberg/extensions/contact-info/address/editor.js new file mode 100644 index 00000000000000..b167dc5cca783d --- /dev/null +++ b/client/gutenberg/extensions/contact-info/address/editor.js @@ -0,0 +1,9 @@ +/** @format */ + +/** + * Internal dependencies + */ +import registerJetpackBlock from 'gutenberg/extensions/presets/jetpack/utils/register-jetpack-block'; +import { name, settings } from '.'; + +registerJetpackBlock( name, settings ); diff --git a/client/gutenberg/extensions/contact-info/address/index.js b/client/gutenberg/extensions/contact-info/address/index.js new file mode 100644 index 00000000000000..ad473c56fc20bc --- /dev/null +++ b/client/gutenberg/extensions/contact-info/address/index.js @@ -0,0 +1,72 @@ +/** @format */ +/** + * External dependencies + */ +import { Path, Circle } from '@wordpress/components'; +import { Fragment } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import edit from './edit'; +import save from './save'; +import renderMaterialIcon from 'gutenberg/extensions/presets/jetpack/utils/render-material-icon'; +import { __, _x } from 'gutenberg/extensions/presets/jetpack/utils/i18n'; + +const attributes = { + address: { + type: 'string', + default: '', + }, + addressLine2: { + type: 'string', + default: '', + }, + addressLine3: { + type: 'string', + default: '', + }, + city: { + type: 'string', + default: '', + }, + region: { + type: 'string', + default: '', + }, + postal: { + type: 'string', + default: '', + }, + country: { + type: 'string', + default: '', + }, + linkToGoogleMaps: { + type: 'boolean', + default: false, + }, +}; + +export const name = 'address'; + +export const settings = { + title: __( 'Address' ), + description: __( 'Lets you add a physical address with Schema markup.' ), + keywords: [ + _x( 'location', 'block search term' ), + _x( 'direction', 'block search term' ), + _x( 'place', 'block search term' ), + ], + icon: renderMaterialIcon( + <Fragment> + <Path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zM7 9c0-2.76 2.24-5 5-5s5 2.24 5 5c0 2.88-2.88 7.19-5 9.88C9.92 16.21 7 11.85 7 9z" /> + <Circle cx="12" cy="9" r="2.5" /> + </Fragment> + ), + category: 'jetpack', + attributes, + parent: [ 'jetpack/contact-info' ], + edit, + save, +}; diff --git a/client/gutenberg/extensions/contact-info/address/save.js b/client/gutenberg/extensions/contact-info/address/save.js new file mode 100644 index 00000000000000..2f7747e94615bd --- /dev/null +++ b/client/gutenberg/extensions/contact-info/address/save.js @@ -0,0 +1,112 @@ +/** @format */ +/** + * External dependencies + */ +import { Fragment } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { __ } from 'gutenberg/extensions/presets/jetpack/utils/i18n'; + +const Address = ( { + attributes: { address, addressLine2, addressLine3, city, region, postal, country }, +} ) => ( + <Fragment> + { address && ( + <div itemprop="streetAddress" class="jetpack-address__address jetpack-address__address1"> + { address } + </div> + ) } + { addressLine2 && ( + <div itemprop="streetAddress" class="jetpack-address__address jetpack-address__address2"> + { addressLine2 } + </div> + ) } + { addressLine3 && ( + <div itemprop="streetAddress" class="jetpack-address__address jetpack-address__address3"> + { addressLine3 } + </div> + ) } + { city && ! ( region || postal ) && ( + <div itemprop="addressLocality" class="jetpack-address__city"> + { city } + </div> + ) } + { city && ( region || postal ) && ( + <div> + { [ + <span itemprop="addressLocality" class="jetpack-address__city"> + { city } + </span>, + ', ', + <span itemprop="addressRegion" class="jetpack-address__region"> + { region } + </span>, + ' ', + <span itemprop="postalCode" class="jetpack-address__postal"> + { postal } + </span>, + ] } + </div> + ) } + { ! city && ( region || postal ) && ( + <div> + { [ + <span itemprop="addressRegion" class="jetpack-address__region"> + { region } + </span>, + ' ', + <span itemprop="postalCode" class="jetpack-address__postal"> + { postal } + </span>, + ] } + </div> + ) } + { country && ( + <div itemprop="addressCountry" class="jetpack-address__country"> + { country } + </div> + ) } + </Fragment> +); + +export const googleMapsUrl = ( { + attributes: { address, addressLine2, addressLine3, city, region, postal, country }, +} ) => { + const addressUrl = address ? `${ address },` : ''; + const addressLine2Url = addressLine2 ? `${ addressLine2 },` : ''; + const addressLine3Url = addressLine3 ? `${ addressLine3 },` : ''; + const cityUrl = city ? `+${ city },` : ''; + let regionUrl = region ? `+${ region },` : ''; + regionUrl = postal ? `${ regionUrl }+${ postal }` : regionUrl; + const countryUrl = country ? `+${ country }` : ''; + + return `https://www.google.com/maps/search/${ addressUrl }${ addressLine2Url }${ addressLine3Url }${ cityUrl }${ regionUrl }${ countryUrl }`.replace( + ' ', + '+' + ); +}; + +const save = props => ( + <div + className={ props.className } + itemprop="address" + itemscope + itemtype="http://schema.org/PostalAddress" + > + { props.attributes.linkToGoogleMaps && ( + <a + href={ googleMapsUrl( props ) } + target="_blank" + rel="noopener noreferrer" + title={ __( 'Open address in Google Maps' ) } + > + <Address { ...props } /> + </a> + ) } + { ! props.attributes.linkToGoogleMaps && <Address { ...props } /> } + </div> +); + +export default save; diff --git a/client/gutenberg/extensions/contact-info/edit.js b/client/gutenberg/extensions/contact-info/edit.js new file mode 100644 index 00000000000000..e156088c08bdff --- /dev/null +++ b/client/gutenberg/extensions/contact-info/edit.js @@ -0,0 +1,54 @@ +/** @format */ + +/** + * External dependencies + */ +import { InnerBlocks } from '@wordpress/editor'; +import classnames from 'classnames'; +/** + * Internal dependencies + */ +const ALLOWED_BLOCKS = [ + 'jetpack/markdown', + 'jetpack/address', + 'jetpack/email', + 'jetpack/phone', + 'jetpack/map', + '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 = props => { + const { + attributes: {}, + isSelected, + } = props; + + return ( + <div + className={ classnames( { + 'jetpack-contact-info-block': true, + 'is-selected': isSelected, + } ) } + > + <InnerBlocks allowedBlocks={ ALLOWED_BLOCKS } templateLock={ false } template={ TEMPLATE } /> + </div> + ); +}; + +export default ContactInfoEdit; diff --git a/client/gutenberg/extensions/contact-info/editor.js b/client/gutenberg/extensions/contact-info/editor.js new file mode 100644 index 00000000000000..3ad6240ae41a32 --- /dev/null +++ b/client/gutenberg/extensions/contact-info/editor.js @@ -0,0 +1,9 @@ +/** @format */ + +/** + * Internal dependencies + */ +import registerJetpackBlock from 'gutenberg/extensions/presets/jetpack/utils/register-jetpack-block'; +import { childBlocks, name, settings } from '.'; + +registerJetpackBlock( name, settings, childBlocks ); diff --git a/client/gutenberg/extensions/contact-info/editor.scss b/client/gutenberg/extensions/contact-info/editor.scss new file mode 100644 index 00000000000000..3cfeff2c948e44 --- /dev/null +++ b/client/gutenberg/extensions/contact-info/editor.scss @@ -0,0 +1,7 @@ +.jetpack-contact-info-block { + padding: 10px 18px; + /* css class added to increase specificity */ + .editor-plain-text.editor-plain-text:focus { + box-shadow: none; + } +} diff --git a/client/gutenberg/extensions/contact-info/email/edit.js b/client/gutenberg/extensions/contact-info/email/edit.js new file mode 100644 index 00000000000000..fd64c949fad14e --- /dev/null +++ b/client/gutenberg/extensions/contact-info/email/edit.js @@ -0,0 +1,17 @@ +/** @format */ + +/** + * Internal dependencies + */ +import save from './save'; +import { __ } from 'gutenberg/extensions/presets/jetpack/utils/i18n'; +import simpleInput from 'gutenberg/extensions/presets/jetpack/utils/simple-input'; + +const EmailEdit = props => { + const { setAttributes } = props; + return simpleInput( 'email', props, __( 'Email' ), save, nextValue => + setAttributes( { email: nextValue } ) + ); +}; + +export default EmailEdit; diff --git a/client/gutenberg/extensions/contact-info/email/editor.js b/client/gutenberg/extensions/contact-info/email/editor.js new file mode 100644 index 00000000000000..b167dc5cca783d --- /dev/null +++ b/client/gutenberg/extensions/contact-info/email/editor.js @@ -0,0 +1,9 @@ +/** @format */ + +/** + * Internal dependencies + */ +import registerJetpackBlock from 'gutenberg/extensions/presets/jetpack/utils/register-jetpack-block'; +import { name, settings } from '.'; + +registerJetpackBlock( name, settings ); diff --git a/client/gutenberg/extensions/contact-info/email/index.js b/client/gutenberg/extensions/contact-info/email/index.js new file mode 100644 index 00000000000000..60ba963143480e --- /dev/null +++ b/client/gutenberg/extensions/contact-info/email/index.js @@ -0,0 +1,42 @@ +/** @format */ +/** + * External dependencies + */ +import { Path } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import edit from './edit'; +import save from './save'; +import renderMaterialIcon from 'gutenberg/extensions/presets/jetpack/utils/render-material-icon'; +import { __, _x } from 'gutenberg/extensions/presets/jetpack/utils/i18n'; + +const attributes = { + email: { + type: 'string', + default: '', + }, +}; + +export const name = 'email'; + +export const settings = { + title: __( 'Email Address' ), + description: __( + 'Lets you add an email address with an automatically generated click-to-email link.' + ), + keywords: [ + 'e-mail', // not translatable on purpose + 'email', // not translatable on purpose + _x( 'message', 'block search term' ), + ], + icon: renderMaterialIcon( + <Path d="M22 6c0-1.1-.9-2-2-2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6zm-2 0l-8 5-8-5h16zm0 12H4V8l8 5 8-5v10z" /> + ), + category: 'jetpack', + attributes, + edit, + save, + parent: [ 'jetpack/contact-info' ], +}; diff --git a/client/gutenberg/extensions/contact-info/email/save.js b/client/gutenberg/extensions/contact-info/email/save.js new file mode 100644 index 00000000000000..007e4cb8bc0386 --- /dev/null +++ b/client/gutenberg/extensions/contact-info/email/save.js @@ -0,0 +1,21 @@ +/** + * Internal dependencies + */ +import textMatchReplace from 'gutenberg/extensions/presets/jetpack/utils/text-match-replace'; + +const renderEmail = inputText => { + return textMatchReplace( + inputText, + /((?:[a-z|0-9+_](?:\.|_\+)*)+[a-z|0-9]\@(?:[a-z|0-9])+(?:(?:\.){0,1}[a-z|0-9]){2}\.[a-z]{2,22})/gim, + ( email, i ) => ( + <a href={ `mailto:${ email }` } key={ i } itemprop="email"> + { email } + </a> + ) + ); +}; + +const save = ( { attributes: { email }, className } ) => + email && <div className={ className }>{ renderEmail( email ) }</div>; + +export default save; diff --git a/client/gutenberg/extensions/contact-info/index.js b/client/gutenberg/extensions/contact-info/index.js new file mode 100644 index 00000000000000..4212a47d43d023 --- /dev/null +++ b/client/gutenberg/extensions/contact-info/index.js @@ -0,0 +1,56 @@ +/** @format */ +/** + * External dependencies + */ +import { InnerBlocks } from '@wordpress/editor'; +import { Path } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import edit from './edit'; +import renderMaterialIcon from 'gutenberg/extensions/presets/jetpack/utils/render-material-icon'; +import { __, _x } from 'gutenberg/extensions/presets/jetpack/utils/i18n'; +import './editor.scss'; +import { name as addressName, settings as addressSettings } from './address/'; +import { name as emailName, settings as emailSettings } from './email/'; +import { name as phoneName, settings as phoneSettings } from './phone/'; + +const attributes = {}; + +const save = ( { className } ) => ( + <div className={ className } itemprop="location" itemscope itemtype="http://schema.org/Place"> + <InnerBlocks.Content /> + </div> +); + +export const name = 'contact-info'; + +export const settings = { + title: __( 'Contact Info' ), + description: __( + 'Lets you add an email address, phone number, and physical address with improved markup for better SEO results.' + ), + keywords: [ + _x( 'email', 'block search term' ), + _x( 'phone', 'block search term' ), + _x( 'address', 'block search term' ), + ], + icon: renderMaterialIcon( + <Path d="M22 3H2C.9 3 0 3.9 0 5v14c0 1.1.9 2 2 2h20c1.1 0 1.99-.9 1.99-2L24 5c0-1.1-.9-2-2-2zm0 16H2V5h20v14zm-2.99-1.01L21 16l-1.51-2h-1.64c-.22-.63-.35-1.3-.35-2s.13-1.37.35-2h1.64L21 8l-1.99-1.99c-1.31.98-2.28 2.37-2.73 3.99-.18.64-.28 1.31-.28 2s.1 1.36.28 2c.45 1.61 1.42 3.01 2.73 3.99zM9 12c1.65 0 3-1.35 3-3s-1.35-3-3-3-3 1.35-3 3 1.35 3 3 3zm0-4c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm6 8.59c0-2.5-3.97-3.58-6-3.58s-6 1.08-6 3.58V18h12v-1.41zM5.48 16c.74-.5 2.22-1 3.52-1s2.77.49 3.52 1H5.48z" /> + ), + category: 'jetpack', + supports: { + align: [ 'wide', 'full' ], + html: false, + }, + attributes, + edit, + save, +}; + +export const childBlocks = [ + { name: addressName, settings: addressSettings }, + { name: emailName, settings: emailSettings }, + { name: phoneName, settings: phoneSettings }, +]; diff --git a/client/gutenberg/extensions/contact-info/phone/edit.js b/client/gutenberg/extensions/contact-info/phone/edit.js new file mode 100644 index 00000000000000..3b7c001f9f2bc1 --- /dev/null +++ b/client/gutenberg/extensions/contact-info/phone/edit.js @@ -0,0 +1,17 @@ +/** @format */ + +/** + * Internal dependencies + */ +import save from './save'; +import { __ } from 'gutenberg/extensions/presets/jetpack/utils/i18n'; +import simpleInput from 'gutenberg/extensions/presets/jetpack/utils/simple-input'; + +const PhoneEdit = props => { + const { setAttributes } = props; + return simpleInput( 'phone', props, __( 'Phone number' ), save, nextValue => + setAttributes( { phone: nextValue } ) + ); +}; + +export default PhoneEdit; diff --git a/client/gutenberg/extensions/contact-info/phone/editor.js b/client/gutenberg/extensions/contact-info/phone/editor.js new file mode 100644 index 00000000000000..b167dc5cca783d --- /dev/null +++ b/client/gutenberg/extensions/contact-info/phone/editor.js @@ -0,0 +1,9 @@ +/** @format */ + +/** + * Internal dependencies + */ +import registerJetpackBlock from 'gutenberg/extensions/presets/jetpack/utils/register-jetpack-block'; +import { name, settings } from '.'; + +registerJetpackBlock( name, settings ); diff --git a/client/gutenberg/extensions/contact-info/phone/index.js b/client/gutenberg/extensions/contact-info/phone/index.js new file mode 100644 index 00000000000000..8715906f3f347a --- /dev/null +++ b/client/gutenberg/extensions/contact-info/phone/index.js @@ -0,0 +1,42 @@ +/** @format */ +/** + * External dependencies + */ +import { Path } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import edit from './edit'; +import save from './save'; +import renderMaterialIcon from 'gutenberg/extensions/presets/jetpack/utils/render-material-icon'; +import { __, _x } from 'gutenberg/extensions/presets/jetpack/utils/i18n'; + +const attributes = { + phone: { + type: 'string', + default: '', + }, +}; + +export const name = 'phone'; + +export const settings = { + title: __( 'Phone Number' ), + description: __( + 'Lets you add a phone number with an automatically generated click-to-call link.' + ), + keywords: [ + _x( 'mobile', 'block search term' ), + _x( 'telephone', 'block search term' ), + _x( 'cell', 'block search term' ), + ], + icon: renderMaterialIcon( + <Path d="M6.54 5c.06.89.21 1.76.45 2.59l-1.2 1.2c-.41-1.2-.67-2.47-.76-3.79h1.51m9.86 12.02c.85.24 1.72.39 2.6.45v1.49c-1.32-.09-2.59-.35-3.8-.75l1.2-1.19M7.5 3H4c-.55 0-1 .45-1 1 0 9.39 7.61 17 17 17 .55 0 1-.45 1-1v-3.49c0-.55-.45-1-1-1-1.24 0-2.45-.2-3.57-.57-.1-.04-.21-.05-.31-.05-.26 0-.51.1-.71.29l-2.2 2.2c-2.83-1.45-5.15-3.76-6.59-6.59l2.2-2.2c.28-.28.36-.67.25-1.02C8.7 6.45 8.5 5.25 8.5 4c0-.55-.45-1-1-1z" /> + ), + category: 'jetpack', + attributes, + parent: [ 'jetpack/contact-info' ], + edit, + save, +}; diff --git a/client/gutenberg/extensions/contact-info/phone/save.js b/client/gutenberg/extensions/contact-info/phone/save.js new file mode 100644 index 00000000000000..9fc1cc3f45f188 --- /dev/null +++ b/client/gutenberg/extensions/contact-info/phone/save.js @@ -0,0 +1,28 @@ +/** + * Internal dependencies + */ +import textMatchReplace from 'gutenberg/extensions/presets/jetpack/utils/text-match-replace'; + +export function renderPhone( inputText ) { + return textMatchReplace( + inputText, + /([0-9\()+]{1}[\ \-().]?[0-9]{1,6}[\ \-().]?[0-9]{0,6}[\ \-()]?[0-9]{0,6}[\ \-().]?[0-9]{0,6}[\ \-().]?[0-9]{0,6}[\ \-().]?[0-9]{0,6})/g, + ( number, i ) => { + if ( number.trim() === '' ) { + return number; + } + const just_number = number.replace( /\D/g, '' ); + + return ( + <a itemprop="telephone" content={ just_number } href={ `tel:${ just_number }` } key={ i }> + { number } + </a> + ); + } + ); +} + +const save = ( { attributes: { phone }, className } ) => + phone && <div className={ className }>{ renderPhone( phone ) }</div>; + +export default save; diff --git a/client/gutenberg/extensions/presets/jetpack/index.json b/client/gutenberg/extensions/presets/jetpack/index.json index 9dec6bae2e0a51..b2f49794b8cfbb 100644 --- a/client/gutenberg/extensions/presets/jetpack/index.json +++ b/client/gutenberg/extensions/presets/jetpack/index.json @@ -13,6 +13,7 @@ ], "beta": [ "mailchimp", - "vr" + "vr", + "contact-info" ] } diff --git a/client/gutenberg/extensions/presets/jetpack/utils/clipboard-input.js b/client/gutenberg/extensions/presets/jetpack/utils/clipboard-input.js new file mode 100644 index 00000000000000..8e9700aac292fa --- /dev/null +++ b/client/gutenberg/extensions/presets/jetpack/utils/clipboard-input.js @@ -0,0 +1,48 @@ +/** + * External dependencies + */ +import { Component } from '@wordpress/element'; +import { ClipboardButton, TextControl } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import { __, _x } from 'gutenberg/extensions/presets/jetpack/utils/i18n'; +import './clipboard-input.scss'; + +class ClipboardInput extends Component { + state = { + hasCopied: false, + }; + + onCopy = () => this.setState( { hasCopied: true } ); + + onFinishCopy = () => this.setState( { hasCopied: false } ); + + onFocus = event => event.target.select(); + + render() { + const { link } = this.props; + const { hasCopied } = this.state; + + if ( ! link ) { + return null; + } + + return ( + <div className="jetpack-clipboard-input"> + <TextControl readOnly onFocus={ this.onFocus } value={ link } /> + <ClipboardButton + isDefault + onCopy={ this.onCopy } + onFinishCopy={ this.onFinishCopy } + text={ link } + > + { hasCopied ? __( 'Copied!' ) : _x( 'Copy', 'verb' ) } + </ClipboardButton> + </div> + ); + } +} + +export default ClipboardInput; diff --git a/client/gutenberg/extensions/presets/jetpack/utils/clipboard-input.scss b/client/gutenberg/extensions/presets/jetpack/utils/clipboard-input.scss new file mode 100644 index 00000000000000..d0118a3efc72c2 --- /dev/null +++ b/client/gutenberg/extensions/presets/jetpack/utils/clipboard-input.scss @@ -0,0 +1,7 @@ +.jetpack-clipboard-input { + display: flex; + + .components-clipboard-button { + margin: 2px 0 0 6px; + } +} diff --git a/client/gutenberg/extensions/presets/jetpack/utils/render-material-icon.jsx b/client/gutenberg/extensions/presets/jetpack/utils/render-material-icon.jsx index b9d64c3444e383..acdc6b7c32cc63 100644 --- a/client/gutenberg/extensions/presets/jetpack/utils/render-material-icon.jsx +++ b/client/gutenberg/extensions/presets/jetpack/utils/render-material-icon.jsx @@ -1,10 +1,14 @@ /** @format */ +/** + * External dependencies + */ +import { Path, SVG } from '@wordpress/components'; const renderMaterialIcon = svg => ( - <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"> - <path fill="none" d="M0 0h24v24H0V0z" /> + <SVG xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"> + <Path fill="none" d="M0 0h24v24H0V0z" /> { svg } - </svg> + </SVG> ); export default renderMaterialIcon; diff --git a/client/gutenberg/extensions/presets/jetpack/utils/simple-input.js b/client/gutenberg/extensions/presets/jetpack/utils/simple-input.js new file mode 100644 index 00000000000000..615e29c1a23d62 --- /dev/null +++ b/client/gutenberg/extensions/presets/jetpack/utils/simple-input.js @@ -0,0 +1,21 @@ +/** + * External dependencies + */ +import { PlainText } from '@wordpress/editor'; + +const simpleInput = ( type, props, placeholder, view, onChange ) => { + const { isSelected } = props; + const value = props.attributes[ type ]; + return ( + <div + className={ isSelected ? `jetpack-${ type }-block is-selected` : `jetpack-${ type }-block` } + > + { ! isSelected && value !== '' && view( props ) } + { ( isSelected || value === '' ) && ( + <PlainText value={ value } placeholder={ placeholder } onChange={ onChange } /> + ) } + </div> + ); +}; + +export default simpleInput; diff --git a/client/gutenberg/extensions/presets/jetpack/utils/text-match-replace.js b/client/gutenberg/extensions/presets/jetpack/utils/text-match-replace.js new file mode 100644 index 00000000000000..2cf8ce55005079 --- /dev/null +++ b/client/gutenberg/extensions/presets/jetpack/utils/text-match-replace.js @@ -0,0 +1,71 @@ +/** @format */ + +/** + * External dependencies + */ +import { isRegExp, escapeRegExp, isString, flatten } from 'lodash'; + +/** + * Given a string, replace every substring that is matched by the `match` regex + * with the result of calling `fn` on matched substring. The result will be an + * array with all odd indexed elements containing the replacements. The primary + * use case is similar to using String.prototype.replace except for React. + * + * React will happily render an array as children of a react element, which + * makes this approach very useful for tasks like surrounding certain text + * within a string with react elements. + * + * Example: + * matchReplace( + * 'Emphasize all phone numbers like 884-555-4443.', + * /([\d|-]+)/g, + * (number, i) => <strong key={i}>{number}</strong> + * ); + * // => ['Emphasize all phone numbers like ', <strong>884-555-4443</strong>, '.' + * + * @param {string} text - The text that you want to replace + * @param {regexp|str} match Must contain a matching group + * @param {function} fn function that helps replace the matched text + * @return {array} An array of string or react components + */ +function replaceString( text, match, fn ) { + let curCharStart = 0; + let curCharLen = 0; + + if ( text === '' ) { + return text; + } else if ( ! text || ! isString( text ) ) { + throw new TypeError( 'First argument must be a string' ); + } + + let re = match; + + if ( ! isRegExp( re ) ) { + re = new RegExp( '(' + escapeRegExp( re ) + ')', 'gi' ); + } + + const result = text.split( re ); + // Apply fn to all odd elements + for ( let i = 1, length = result.length; i < length; i += 2 ) { + curCharLen = result[ i ].length; + curCharStart += result[ i - 1 ].length; + if ( result[ i ] ) { + result[ i ] = fn( result[ i ], i, curCharStart ); + } + curCharStart += curCharLen; + } + + return result; +} + +const textMatchReplace = ( source, match, fn ) => { + if ( ! Array.isArray( source ) ) source = [ source ]; + + return flatten( + source.map( x => { + return isString( x ) ? replaceString( x, match, fn ) : x; + } ) + ); +}; + +export default textMatchReplace; diff --git a/client/gutenberg/extensions/shortlinks/editor.scss b/client/gutenberg/extensions/shortlinks/editor.scss deleted file mode 100644 index b7e8e74e3df55f..00000000000000 --- a/client/gutenberg/extensions/shortlinks/editor.scss +++ /dev/null @@ -1,10 +0,0 @@ -.jetpack-shortlinks__panel { - .components-base-control { - display: inline-block; - margin-right: 4px; - } - - .components-clipboard-button { - margin-top: 1px; - } -}