Skip to content
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

Reusable Blocks: Support importing and exporting reusable blocks #9788

Merged
merged 8 commits into from
Sep 14, 2018
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions docs/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@
{
"title": "Articles",
"slug": "articles",
"markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/outreach/articles.md",
"markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/outreach/docs/articles.md",
"parent": "outreach"
},
{
Expand Down Expand Up @@ -431,6 +431,12 @@
"markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/library-export-default-webpack-plugin/README.md",
"parent": "packages"
},
{
"title": "@wordpress/list-reusable-blocks",
"slug": "packages-list-reusable-blocks",
"markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/list-reusable-blocks/README.md",
"parent": "packages"
},
{
"title": "@wordpress/npm-package-json-lint-config",
"slug": "packages-npm-package-json-lint-config",
Expand Down Expand Up @@ -857,4 +863,4 @@
"markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/data/data-core-viewport.md",
"parent": "data"
}
]
]
8 changes: 7 additions & 1 deletion edit-post/components/header/more-menu/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* WordPress dependencies
*/
import { __, _x } from '@wordpress/i18n';
import { IconButton, Dropdown, MenuGroup } from '@wordpress/components';
import { IconButton, Dropdown, MenuGroup, MenuItem } from '@wordpress/components';
import { Fragment } from '@wordpress/element';

/**
Expand Down Expand Up @@ -37,6 +37,12 @@ const MoreMenu = () => (
label={ __( 'Tools' ) }
filterName="editPost.MoreMenu.tools"
>
<MenuItem
role="menuitem"
href="edit.php?post_type=wp_block"
>
{ __( 'Manage All Reusable Blocks' ) }
</MenuItem>
<TipsToggle onToggle={ onClose } />
<KeyboardShortcutsHelpMenuItem onSelect={ onClose } />
</MenuGroup>
Expand Down
6 changes: 6 additions & 0 deletions gutenberg.php
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,12 @@ function gutenberg_add_edit_link( $actions, $post ) {
if ( 'wp_block' === $post->post_type ) {
unset( $actions['edit'] );
unset( $actions['inline hide-if-no-js'] );
$actions['export'] = sprintf(
'<a class="wp-list-reusable-blocks__export" href="#" data-id="%s" aria-label="%s">%s</a>',
$post->ID,
__( 'Export as JSON', 'gutenberg' ),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this convention lifted from somewhere? Doesn't seem obvious why we'd need an aria-label which has the same text as the element itself.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, it's inspired by the way we add the "Classic editor" link in the post list actions

__( 'Export as JSON', 'gutenberg' )
);
return $actions;
}

Expand Down
38 changes: 38 additions & 0 deletions lib/client-assets.php
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,22 @@ function gutenberg_register_scripts_and_styles() {
true
);

wp_register_script(
'wp-list-reusable-blocks',
gutenberg_url( 'build/list-reusable-blocks/index.js' ),
array(
'lodash',
'wp-api-fetch',
'wp-components',
'wp-compose',
'wp-element',
'wp-i18n',
'wp-polyfill-ecmascript',
),
filemtime( gutenberg_dir_path() . 'build/list-reusable-blocks/index.js' ),
true
);

// Editor Styles.
// This empty stylesheet is defined to ensure backwards compatibility.
wp_register_style( 'wp-blocks', false );
Expand Down Expand Up @@ -718,6 +734,14 @@ function gutenberg_register_scripts_and_styles() {
);
wp_style_add_data( 'wp-block-library-theme', 'rtl', 'replace' );

wp_register_style(
'wp-list-reusable-blocks',
gutenberg_url( 'build/list-reusable-blocks/style.css' ),
array( 'wp-components' ),
filemtime( gutenberg_dir_path() . 'build/list-reusable-blocks/style.css' )
);
wp_style_add_data( 'wp-list-reusable-block', 'rtl', 'replace' );

if ( defined( 'GUTENBERG_LIVE_RELOAD' ) && GUTENBERG_LIVE_RELOAD ) {
$live_reload_url = ( GUTENBERG_LIVE_RELOAD === true ) ? 'http://localhost:35729/livereload.js' : GUTENBERG_LIVE_RELOAD;

Expand Down Expand Up @@ -1513,3 +1537,17 @@ function gutenberg_editor_scripts_and_styles( $hook ) {
*/
do_action( 'enqueue_block_editor_assets' );
}

/**
* Enqueue the reusable blocks listing page's script
*
* @param string $hook Screen name.
*/
function wp_load_list_reusable_blocks( $hook ) {
$is_reusable_blocks_list_page = 'edit.php' === $hook && isset( $_GET['post_type'] ) && 'wp_block' === $_GET['post_type'];
if ( $is_reusable_blocks_list_page ) {
wp_enqueue_script( 'wp-list-reusable-blocks' );
wp_enqueue_style( 'wp-list-reusable-blocks' );
}
}
add_action( 'admin_enqueue_scripts', 'wp_load_list_reusable_blocks' );
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 1 addition & 3 deletions packages/components/src/menu-item/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import IconButton from '../icon-button';
*
* @return {WPElement} More menu item.
*/
function MenuItem( { children, className, icon, onClick, shortcut, isSelected, role = 'menuitem', ...props } ) {
function MenuItem( { children, className, icon, shortcut, isSelected, role = 'menuitem', ...props } ) {
className = classnames( 'components-menu-item__button', className, {
'has-icon': icon,
} );
Expand All @@ -39,7 +39,6 @@ function MenuItem( { children, className, icon, onClick, shortcut, isSelected, r
<IconButton
className={ className }
icon={ icon }
onClick={ onClick }
aria-checked={ isSelected }
role={ role }
{ ...props }
Expand All @@ -53,7 +52,6 @@ function MenuItem( { children, className, icon, onClick, shortcut, isSelected, r
return (
<Button
className={ className }
onClick={ onClick }
aria-checked={ isSelected }
role={ role }
{ ...props }
Expand Down
1 change: 1 addition & 0 deletions packages/list-reusable-blocks/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package-lock=false
5 changes: 5 additions & 0 deletions packages/list-reusable-blocks/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Reusable blocks listing page

Package used to add import/export links to the listing page of the reusable blocks.

<br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p>
34 changes: 34 additions & 0 deletions packages/list-reusable-blocks/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"name": "@wordpress/list-reusable-blocks",
"version": "1.0.0",
"description": "Adding Export/Import support to the reusable blocks listing.",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really stress-testing our policy of publishing all the things as modules, huh 😅

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made it private :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that I think about it. Making it private means it can't be included in Core as an external package and begs the question of whether it should be moved to Core or kept in the repo post-merge.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean, I never said I was opposed to it being published to npm 😉

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, I know it was private even before the comment :)

"author": "The WordPress Contributors",
"license": "GPL-2.0-or-later",
"keywords": [
"templates",
"reusable blocks"
],
"private": true,
"homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/list-reusable-blocks/README.md",
"repository": {
"type": "git",
"url": "https://github.com/WordPress/gutenberg.git"
},
"bugs": {
"url": "https://github.com/WordPress/gutenberg/issues"
},
"main": "build/index.js",
"module": "build-module/index.js",
"dependencies": {
"@babel/runtime": "^7.0.0",
"@wordpress/api-fetch": "file:../api-fetch",
"@wordpress/components": "file:../components",
"@wordpress/compose": "file:../compose",
"@wordpress/element": "file:../element",
"@wordpress/i18n": "file:../i18n",
"lodash": "^4.17.10"
},
"publishConfig": {
"access": "public"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* External dependencies
*/
import { flow } from 'lodash';

/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { Dropdown, Button } from '@wordpress/components';

/**
* Internal dependencies
*/
import ImportForm from '../import-form';

function ImportDropdown( { onUpload } ) {
return (
<Dropdown
position="bottom right"
contentClassName="list-reusable-blocks-import-dropdown__content"
renderToggle={ ( { isOpen, onToggle } ) => (
<Button
type="button"
aria-expanded={ isOpen }
onClick={ onToggle }
isPrimary
>
{ __( 'Import from JSON' ) }
</Button>
) }
renderContent={ ( { onClose } ) => (
<ImportForm onUpload={ flow( onClose, onUpload ) } />
) }
/>
);
}

export default ImportDropdown;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.list-reusable-blocks-import-dropdown__content .components-popover__content {
padding: 10px;
}
101 changes: 101 additions & 0 deletions packages/list-reusable-blocks/src/components/import-form/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/**
* WordPress dependencies
*/
import { Component } from '@wordpress/element';
import { withInstanceId } from '@wordpress/compose';
import { __ } from '@wordpress/i18n';
import { Button, Notice } from '@wordpress/components';

/**
* Internal dependencies
*/
import importReusableBlock from '../../utils/import';

class ImportForm extends Component {
constructor() {
super( ...arguments );
this.state = {
isLoading: false,
error: null,
file: null,
};

this.isStillMounted = true;
this.onChangeFile = this.onChangeFile.bind( this );
this.onSubmit = this.onSubmit.bind( this );
}

componentWillUnmount() {
this.isStillMounted = false;
}

onChangeFile( event ) {
this.setState( { file: event.target.files[ 0 ] } );
}

onSubmit( event ) {
event.preventDefault();
const { file } = this.state;
const { onUpload } = this.props;
if ( ! file ) {
return;
}
this.setState( { isLoading: true } );
importReusableBlock( file )
.then( ( reusableBlock ) => {
if ( ! this.isStillMounted ) {
return;
}

this.setState( { isLoading: false } );
onUpload( reusableBlock );
} )
.catch( ( error ) => {
if ( ! this.isStillMounted ) {
return;
}

this.setState( { isLoading: false, error: error.message } );
} );
}

render() {
const { instanceId } = this.props;
const { file, isLoading, error } = this.state;
const inputId = 'list-reusable-blocks-import-form-' + instanceId;
return (
<form
className="list-reusable-blocks-import-form"
onSubmit={ this.onSubmit }
>
{ error && (
<Notice status="error">
{ error }
</Notice>
) }
<label
htmlFor={ inputId }
className="list-reusable-blocks-import-form__label"
>
{ __( 'File' ) }
</label>
<input
id={ inputId }
type="file"
onChange={ this.onChangeFile }
/>
<Button
type="submit"
isBusy={ isLoading }
disabled={ ! file || isLoading }
isDefault
className="list-reusable-blocks-import-form__button"
>
{ __( 'Import' ) }
</Button>
</form>
);
}
}

export default withInstanceId( ImportForm );
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.list-reusable-blocks-import-form__label {
display: block;
margin-bottom: 10px;
}

.list-reusable-blocks-import-form__button {
margin-top: 20px;
float: right;
}

.list-reusable-blocks-import-form .components-notice__content {
margin: 0;
}
Loading