-
Notifications
You must be signed in to change notification settings - Fork 812
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Extensions: Add block editor (Gutenberg) extensions source #11633
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
200
extensions/blocks/business-hours/components/day-edit.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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' ) }> </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"> </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' ) }> </div> | ||
<div className="business-hours__remove"> </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
58
extensions/blocks/business-hours/components/day-preview.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In #11333 (unmerged) @ockham suggested separate folder for Plugins:
extensions/plugins/seo/seo.php
Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suppose extension could be both plugins and blocks (and filters and whatnot) at the same time, so it might not make sense to optimize this way?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It may be easier for our build tools and for new contributors if everything was in one place?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does
/extensions/blocks
then make sense and should we just have/extensions
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe the things in
/blocks
are actually blocks.#11640 includes additional extensions (the sidebar and category) that aren't blocks. They are not located under blocks.