Skip to content

Commit

Permalink
Extensions: Add block editor extensions source (#11685)
Browse files Browse the repository at this point in the history
* Revert "Gutenblocks: Revert move to JP (#11682)"

This reverts commit 9deff5e.

* Pull changes from wp-calypso
  • Loading branch information
kraftbj authored Mar 26, 2019
1 parent 8d8cbec commit 43f0264
Show file tree
Hide file tree
Showing 226 changed files with 15,396 additions and 150 deletions.
13 changes: 13 additions & 0 deletions .svnignore
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,16 @@ yarn.lock
docker
bin/pre-commit-hook.js
yarn-error.log
extensions/**/*.css
extensions/**/*.gif
extensions/**/*.jpeg
extensions/**/*.jpg
extensions/**/*.js
extensions/**/*.json
extensions/**/*.jsx
extensions/**/*.md
extensions/**/*.png
extensions/**/*.sass
extensions/**/*.scss
extensions/**/*.svg
**/__snapshots__
42 changes: 35 additions & 7 deletions bin/build-asset-cdn-json.php
Original file line number Diff line number Diff line change
@@ -1,20 +1,48 @@
<?php

$path = dirname( dirname( __FILE__ ) ) . '/';
// The repo root path.
$path = dirname( dirname( __FILE__ ) ) . '/';

// Build an iterator over all files in the repo that match the regex in the RegexIterator.
$directory = new RecursiveDirectoryIterator( $path );
$iterator = new RecursiveIteratorIterator( $directory );
$regex = new RegexIterator( $iterator, '/^.+\.(css|js)$/i', RecursiveRegexIterator::GET_MATCH );

$ignore_paths = array(
'_inc/client/',
'bin/',
'docker/',
'docs/',
'extensions/',
'logs/',
'node_modules/',
'tests/',
'tools/',
'vendor/',
);

$manifest = array();
foreach ( $regex as $file => $value ) {
$file = str_replace( $path, '', $file );
$directory = substr( $file, 0, strpos( $file, '/' ) );
if ( in_array( $directory, array( 'node_modules', 'tests' ) ) ) {
foreach ( $regex as $path_to_file => $value ) {
$path_from_repo_root = str_replace( $path, '', $path_to_file );

// Ignore top-level files.
if ( false === strpos( $path_from_repo_root, '/' ) ) {
continue;
}
$manifest[] = $file;

// Ignore explicit ignore list.
foreach ( $ignore_paths as $ignore_path ) {
if ( 0 === strpos( $path_from_repo_root, $ignore_path ) ) {
continue 2;
}
}

$manifest[] = $path_from_repo_root;
}

$export = var_export( $manifest, true );

file_put_contents( $path . 'modules/photon-cdn/jetpack-manifest.php', "<?php \r\n\$assets = $export;\r\n" );
file_put_contents( $path . 'modules/photon-cdn/jetpack-manifest.php', "<?php
// This file is autogenerated by bin/build-asset-cdn-json.php
\$assets = $export;\r\n" );
1 change: 1 addition & 0 deletions class.jetpack-gutenberg.php
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,7 @@ public static function enqueue_block_editor_assets() {
'wp-edit-post',
'wp-editor',
'wp-element',
'wp-escape-html',
'wp-hooks',
'wp-i18n',
'wp-keycodes',
Expand Down
28 changes: 13 additions & 15 deletions extensions/README.md
Original file line number Diff line number Diff line change
@@ -1,40 +1,38 @@
# Jetpack Block Editor Extensions

This directory lists extensions for the Block Editor, also known as Gutenberg, [that was introduced in WordPress 5.0](https://wordpress.org/news/2018/12/bebo/).
This directory lists extensions for the Block Editor, also known as Gutenberg,
[that was introduced in WordPress 5.0](https://wordpress.org/news/2018/12/bebo/).

## Extension Type

We define different types of block editor extensions:

- Blocks are available in the editor itself, and live in the `blocks` directory.
- Plugins are available in the Jetpack sidebar that appears on the right side of the block editor. Those live in the `plugins` directory.

When adding a new extension, add a new directory for your extension the matching directory.
- Blocks are available in the editor itself.
- Plugins are available in the Jetpack sidebar that appears on the right side of the block editor.

## Extension Structure

Your extension should follow this structure:
Extensions loosely follow this structure:

```
.
└── blockname/
└── blockname.php ← PHP file where the block and its assets are registered.
└── block-or-plugin-name/
├── block-or-plugin-name.php ← PHP file where the block and its assets are registered.
├── editor.js ← script loaded only in the editor
├── editor.scss ← styles loaded only in the editor
├── view.js ← script loaded in the editor and theme
└── view.scss ← styles loaded in the editor and theme
```

If your block depends on another block, place them all in extensions folder:

```
.
├── blockname/
├── block-name/
└── sub-blockname/
```

**Note that this directory is still being populated. For now, you can find the blocks [here](https://github.com/Automattic/wp-calypso/tree/master/client/gutenberg/extensions).

## Develop new blocks

You can follow [the instructions here](../docs/guides/gutenberg-blocks.md) to add your own block to Jetpack.

## Block naming conventions
Coming when [#11640](https://github.com/Automattic/jetpack/pull/11640) lands.

Blocks should use the `jetpack/` prefix, e.g. `jetpack/markdown`.
200 changes: 200 additions & 0 deletions extensions/blocks/business-hours/components/day-edit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
/**
* External dependencies
*/
import { isEmpty } from 'lodash';
import { Component, Fragment } from '@wordpress/element';
import { IconButton, TextControl, ToggleControl } from '@wordpress/components';
import classNames from 'classnames';

/**
* Internal dependencies
*/
import { __ } from '../../../utils/i18n';

const defaultOpen = '09:00';
const defaultClose = '17:00';

class DayEdit extends Component {
renderInterval = ( interval, intervalIndex ) => {
const { day } = this.props;
const { opening, closing } = interval;
return (
<Fragment key={ intervalIndex }>
<div className="business-hours__row">
<div className={ classNames( day.name, 'business-hours__day' ) }>
{ intervalIndex === 0 && this.renderDayToggle() }
</div>
<div className={ classNames( day.name, 'business-hours__hours' ) }>
<TextControl
type="time"
label={ __( 'Opening' ) }
value={ opening }
className="business-hours__open"
placeholder={ defaultOpen }
onChange={ value => {
this.setHour( value, 'opening', intervalIndex );
} }
/>
<TextControl
type="time"
label={ __( 'Closing' ) }
value={ closing }
className="business-hours__close"
placeholder={ defaultClose }
onChange={ value => {
this.setHour( value, 'closing', intervalIndex );
} }
/>
</div>
<div className="business-hours__remove">
{ day.hours.length > 1 && (
<IconButton
isSmall
isLink
icon="trash"
onClick={ () => {
this.removeInterval( intervalIndex );
} }
/>
) }
</div>
</div>
{ intervalIndex === day.hours.length - 1 && (
<div className="business-hours__row business-hours-row__add">
<div className={ classNames( day.name, 'business-hours__day' ) }>&nbsp;</div>
<div className={ classNames( day.name, 'business-hours__hours' ) }>
<IconButton isLink label={ __( 'Add Hours' ) } onClick={ this.addInterval }>
{ __( 'Add Hours' ) }
</IconButton>
</div>
<div className="business-hours__remove">&nbsp;</div>
</div>
) }
</Fragment>
);
};

setHour = ( hourValue, hourType, hourIndex ) => {
const { day, attributes, setAttributes } = this.props;
const { days } = attributes;
setAttributes( {
days: days.map( value => {
if ( value.name === day.name ) {
return {
...value,
hours: value.hours.map( ( hour, index ) => {
if ( index === hourIndex ) {
return {
...hour,
[ hourType ]: hourValue,
};
}
return hour;
} ),
};
}
return value;
} ),
} );
};

toggleClosed = nextValue => {
const { day, attributes, setAttributes } = this.props;
const { days } = attributes;

setAttributes( {
days: days.map( value => {
if ( value.name === day.name ) {
const hours = nextValue
? [
{
opening: defaultOpen,
closing: defaultClose,
},
]
: [];
return {
...value,
hours,
};
}
return value;
} ),
} );
};

addInterval = () => {
const { day, attributes, setAttributes } = this.props;
const { days } = attributes;
day.hours.push( { opening: '', closing: '' } );
setAttributes( {
days: days.map( value => {
if ( value.name === day.name ) {
return {
...value,
hours: day.hours,
};
}
return value;
} ),
} );
};

removeInterval = hourIndex => {
const { day, attributes, setAttributes } = this.props;
const { days } = attributes;

setAttributes( {
days: days.map( value => {
if ( day.name === value.name ) {
return {
...value,
hours: value.hours.filter( ( hour, index ) => {
return hourIndex !== index;
} ),
};
}
return value;
} ),
} );
};

isClosed() {
const { day } = this.props;
return isEmpty( day.hours );
}

renderDayToggle() {
const { day, localization } = this.props;
return (
<Fragment>
<span className="business-hours__day-name">{ localization.days[ day.name ] }</span>
<ToggleControl
label={ this.isClosed() ? __( 'Closed' ) : __( 'Open' ) }
checked={ ! this.isClosed() }
onChange={ this.toggleClosed }
/>
</Fragment>
);
}

renderClosed() {
const { day } = this.props;
return (
<div className="business-hours__row business-hours-row__closed">
<div className={ classNames( day.name, 'business-hours__day' ) }>
{ this.renderDayToggle() }
</div>
<div className={ classNames( day.name, 'closed', 'business-hours__hours' ) }>&nbsp;</div>
<div className="business-hours__remove">&nbsp;</div>
</div>
);
}

render() {
const { day } = this.props;
return this.isClosed() ? this.renderClosed() : day.hours.map( this.renderInterval );
}
}

export default DayEdit;
58 changes: 58 additions & 0 deletions extensions/blocks/business-hours/components/day-preview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
* External dependencies
*/
import { Component, Fragment } from '@wordpress/element';
import { date } from '@wordpress/date';
import { isEmpty } from 'lodash';
import { sprintf } from '@wordpress/i18n';

/**
* Internal dependencies
*/
import { _x } from '../../../utils/i18n';

class DayPreview extends Component {
formatTime( time ) {
const { timeFormat } = this.props;
const [ hours, minutes ] = time.split( ':' );
const _date = new Date();
if ( ! hours || ! minutes ) {
return false;
}
_date.setHours( hours );
_date.setMinutes( minutes );
return date( timeFormat, _date );
}

renderInterval = ( interval, key ) => {
return (
<dd key={ key }>
{ sprintf(
_x( 'From %s to %s', 'from business opening hour to closing hour' ),
this.formatTime( interval.opening ),
this.formatTime( interval.closing )
) }
</dd>
);
};

render() {
const { day, localization } = this.props;
const hours = day.hours.filter(
// remove any malformed or empty intervals
interval => this.formatTime( interval.opening ) && this.formatTime( interval.closing )
);
return (
<Fragment>
<dt className={ day.name }>{ localization.days[ day.name ] }</dt>
{ isEmpty( hours ) ? (
<dd>{ _x( 'Closed', 'business is closed on a full day' ) }</dd>
) : (
hours.map( this.renderInterval )
) }
</Fragment>
);
}
}

export default DayPreview;
Loading

0 comments on commit 43f0264

Please sign in to comment.