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

Add "Convert to blocks" option in HTML block #7667

Merged
merged 10 commits into from
Jul 10, 2018
50 changes: 50 additions & 0 deletions editor/components/block-settings-menu/html-converter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { IconButton } from '@wordpress/components';
import { rawHandler, getBlockContent } from '@wordpress/blocks';
import { compose } from '@wordpress/element';
import { withSelect, withDispatch } from '@wordpress/data';

export function HTMLConverter( { block, onReplace, small, canUserUseUnfilteredHTML, role } ) {
if ( ! block || block.name !== 'core/html' ) {
return null;
}

const label = __( 'Convert to blocks' );

const convertToBlocks = () => {
Copy link
Member

Choose a reason for hiding this comment

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

Is it possible to move it to withDispatch or is there any blocker?

Copy link
Member Author

Choose a reason for hiding this comment

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

I guess it could be refactored, indeed. I don't mind one-way or the other and did that to mimic what existed in UnknownConverter. I'll leave it as it is, we can always refactor both later if necessary.

Copy link
Member

Choose a reason for hiding this comment

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

It's a leaf component inside the dropdown which is not rendered by default, so this shouldn't impact performance. In general, having a local function inside render method isn't expected because it causes unnecessary re-renders.

Copy link
Member Author

Choose a reason for hiding this comment

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

Grzegorz, #7885 follows up the work on this PR and addresses some ideas you mentioned in this thread (share common code, move to withDispatch).

onReplace( block.uid, rawHandler( {
HTML: getBlockContent( block ),
mode: 'BLOCKS',
canUserUseUnfilteredHTML,
} ) );
};

return (
<IconButton
className="editor-block-settings-menu__control"
onClick={ convertToBlocks }
icon="screenoptions"
label={ small ? label : undefined }
role={ role }
>
{ ! small && label }
</IconButton>
);
}

export default compose(
withSelect( ( select, { uid } ) => {
const { getBlock, getCurrentPostType, canUserUseUnfilteredHTML } = select( 'core/editor' );
return {
block: getBlock( uid ),
postType: getCurrentPostType(),
canUserUseUnfilteredHTML: canUserUseUnfilteredHTML(),
};
} ),
withDispatch( ( dispatch ) => ( {
onReplace: dispatch( 'core/editor' ).replaceBlocks,
} ) ),
)( HTMLConverter );
2 changes: 2 additions & 0 deletions editor/components/block-settings-menu/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import BlockRemoveButton from './block-remove-button';
import SharedBlockConvertButton from './shared-block-convert-button';
import SharedBlockDeleteButton from './shared-block-delete-button';
import UnknownConverter from './unknown-converter';
import HTMLConverter from './html-converter';
import _BlockSettingsMenuFirstItem from './block-settings-menu-first-item';

export class BlockSettingsMenu extends Component {
Expand Down Expand Up @@ -95,6 +96,7 @@ export class BlockSettingsMenu extends Component {
<_BlockSettingsMenuFirstItem.Slot fillProps={ { onClose } } />
{ count === 1 && <BlockModeToggle uid={ firstBlockUID } onToggle={ onClose } role="menuitem" /> }
{ count === 1 && <UnknownConverter uid={ firstBlockUID } role="menuitem" /> }
{ count === 1 && <HTMLConverter uid={ firstBlockUID } role="menuitem" /> }
<BlockDuplicateButton uids={ uids } rootUID={ rootUID } role="menuitem" />
{ count === 1 && <SharedBlockConvertButton uid={ firstBlockUID } onToggle={ onClose } itemsRole="menuitem" /> }
<div className="editor-block-settings-menu__separator" />
Expand Down
11 changes: 11 additions & 0 deletions editor/store/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -1891,3 +1891,14 @@ export function getTokenSettings( state, name ) {

return state.tokens[ name ];
}

/**
* Returns whether or not the user has the unfiltered_html capability.
*
* @param {Object} state Editor state.
*
* @return {boolean} Whether the user can or can't post unfiltered HTML.
*/
export function canUserUseUnfilteredHTML( state ) {
return has( getCurrentPost( state ), [ '_links', 'wp:action-unfiltered_html' ] );
}
22 changes: 22 additions & 0 deletions editor/store/test/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { moment } from '@wordpress/date';
import * as selectors from '../selectors';

const {
canUserUseUnfilteredHTML,
hasEditorUndo,
hasEditorRedo,
isEditedPostNew,
Expand Down Expand Up @@ -3809,4 +3810,25 @@ describe( 'selectors', () => {
expect( getBlockListSettings( state, 'chicken' ) ).toBe( undefined );
} );
} );

describe( 'canUserUseUnfilteredHTML', () => {
it( 'should return true if the _links object contains the property wp:action-unfiltered_html', () => {
const state = {
currentPost: {
_links: {
'wp:action-unfiltered_html': [],
},
},
};
expect( canUserUseUnfilteredHTML( state ) ).toBe( true );
} );
it( 'should return false if the _links object doesnt contain the property wp:action-unfiltered_html', () => {
const state = {
currentPost: {
_links: {},
},
};
expect( canUserUseUnfilteredHTML( state ) ).toBe( false );
} );
} );
} );
16 changes: 16 additions & 0 deletions lib/rest-api.php
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,22 @@ function gutenberg_add_target_schema_to_links( $response, $post, $request ) {
);
}
}
if ( 'edit' === $request['context'] && current_user_can( 'unfiltered_html' ) ) {
$new_links['https://api.w.org/action-unfiltered_html'] = array(
array(
'title' => __( 'The current user can post HTML markup and JavaScript.', 'gutenberg' ),
'href' => $orig_href,
'targetSchema' => array(
'type' => 'object',
'properties' => array(
'unfiltered_html' => array(
'type' => 'boolean',
),
),
),
),
);
}
if ( 'edit' === $request['context'] ) {
if ( current_user_can( $post_type->cap->publish_posts ) ) {
$new_links['https://api.w.org/action-publish'] = array(
Expand Down
45 changes: 45 additions & 0 deletions phpunit/class-gutenberg-rest-api-test.php
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,51 @@ function test_viewable_field_without_context() {
$this->assertFalse( isset( $result['viewable'] ) );
}

/**
* Only returns wp:action-unfiltered_html when current user can use unfiltered HTML.
* See https://codex.wordpress.org/Roles_and_Capabilities#Capability_vs._Role_Table
*/
function test_link_unfiltered_html() {
$post_id = $this->factory->post->create();
$check_key = 'https://api.w.org/action-unfiltered_html';
// admins can in a single site, but can't in a multisite.
wp_set_current_user( $this->administrator );
$request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . $post_id );
$request->set_param( 'context', 'edit' );
$response = rest_do_request( $request );
$links = $response->get_links();
if ( is_multisite() ) {
$this->assertFalse( isset( $links[ $check_key ] ) );
} else {
$this->assertTrue( isset( $links[ $check_key ] ) );
}
// authors can't.
wp_set_current_user( $this->author );
$request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . $post_id );
$request->set_param( 'context', 'edit' );
$response = rest_do_request( $request );
$links = $response->get_links();
$this->assertFalse( isset( $links[ $check_key ] ) );
// editors can in a single site, but can't in a multisite.
wp_set_current_user( $this->editor );
$request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . $post_id );
$request->set_param( 'context', 'edit' );
$response = rest_do_request( $request );
$links = $response->get_links();
if ( is_multisite() ) {
$this->assertFalse( isset( $links[ $check_key ] ) );
} else {
$this->assertTrue( isset( $links[ $check_key ] ) );
}
// contributors can't.
wp_set_current_user( $this->contributor );
$request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . $post_id );
$request->set_param( 'context', 'edit' );
$response = rest_do_request( $request );
$links = $response->get_links();
$this->assertFalse( isset( $links[ $check_key ] ) );
}

/**
* Only returns wp:action-assign-author when current user can assign author.
*/
Expand Down