Skip to content

Commit

Permalink
Reusable Blocks: Support importing and exporting reusable blocks (#9788)
Browse files Browse the repository at this point in the history
* Reusable Blocks: Support importing and exporting reusable blocks

* Add the "manage reusable blocks" link to the more menu

* Fix success notice

* explicitely tag the exported JSON file

* Adding import reusable block e2e test

* Rename isMounted to avoid conflicts with React

* small code style change

* Avoid translating exception messages
  • Loading branch information
youknowriad committed Sep 14, 2018
1 parent 546b744 commit ff4bc70
Show file tree
Hide file tree
Showing 21 changed files with 486 additions and 7 deletions.
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' ),
__( '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.",
"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;
}
113 changes: 113 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,113 @@
/**
* 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;
}

let uiMessage;
switch ( error.message ) {
case 'Invalid JSON file':
uiMessage = __( 'Invalid JSON file' );
break;
case 'Invalid Reusable Block JSON file':
uiMessage = __( 'Invalid Reusable Block JSON file' );
break;
default:
uiMessage = __( 'Unknow error' );
}

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

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

0 comments on commit ff4bc70

Please sign in to comment.