Skip to content

Commit

Permalink
Add source for Gutenberg extensions (#11633)
Browse files Browse the repository at this point in the history
The source for block editor (Gutenberg) extensions was stored in the
Calypso repository:

https://github.com/Automattic/wp-calypso

Move the source into this repository.
  • Loading branch information
roccotripaldi authored Mar 25, 2019
1 parent 0d14622 commit a2bf967
Show file tree
Hide file tree
Showing 223 changed files with 15,209 additions and 15 deletions.
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 a2bf967

Please sign in to comment.