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 Gutenberg 'Enable AMP' toggle #1275

Merged
merged 20 commits into from
Aug 1, 2018
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
5fb8d66
Begin Gutenberg 'Enabled AMP' toggle.
kienstra Jul 21, 2018
9f57e67
Refactor JS file to use a module patter.
kienstra Jul 22, 2018
a06e32d
Abstract logic to get enabled status into helper method.
kienstra Jul 22, 2018
c328564
Add assertions for inline script.
kienstra Jul 22, 2018
acc6467
Improve documentation in amp-block-editor-toggle.js.
kienstra Jul 22, 2018
c05e469
Address Travis error by removing blank line.
kienstra Jul 22, 2018
5ed6837
Take first step to enable compiling amp-block-editor-togge.js
kienstra Jul 28, 2018
69d6c77
Begin to convert AMP block editor toggle to a module.
kienstra Jul 28, 2018
46723ad
Merge branch 'develop' into add/editor-toggle
kienstra Jul 28, 2018
f59170c
Improve documentation, simplify conditional.
kienstra Jul 28, 2018
25156f4
Localize values for amp-block-editor-toggle.js
kienstra Jul 30, 2018
d5378c5
Move error messages into function, localize them into script
kienstra Jul 30, 2018
0735ed0
Output Notices with links in the block editor.
kienstra Jul 31, 2018
f8c3c09
Improve documentation for Notices
kienstra Jul 31, 2018
8b5d749
Use wildcards in .eslintignore and .jshintignore
kienstra Jul 31, 2018
c7bc94c
Only call gutenberg_get_jed_locale_data() if it exists.
kienstra Jul 31, 2018
9f0e248
Simply pass value to function without storing in variable
kienstra Jul 31, 2018
b76886a
Simplify error messages to use RawHTML.
kienstra Aug 1, 2018
230d87a
Fix test_get_error_messages() to apply latest change.
kienstra Aug 1, 2018
c4da67b
Fix error raised when post resource lacks meta
westonruter Aug 1, 2018
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
2 changes: 1 addition & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
**/*.min.js
**/node_modules/**
**/vendor/**
**/assets/js/amp-blocks-compiled.js
**/assets/js/*-compiled.js
2 changes: 1 addition & 1 deletion .jshintignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
**/*.min.js
**/node_modules/**
**/vendor/**
**/assets/js/amp-blocks-compiled.js
**/assets/js/*-compiled.js
115 changes: 115 additions & 0 deletions assets/src/amp-block-editor-toggle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/**
* WordPress dependencies
*/
const { __ } = wp.i18n;
const { FormToggle, Notice } = wp.components;
const { Fragment } = wp.element;
const { withSelect, withDispatch } = wp.data;
const { PluginPostStatusInfo } = wp.editPost;
const { compose, withInstanceId } = wp.compose;

/**
* Exported via wp_localize_script().
*/
const { possibleStati, defaultStatus, errorMessages } = window.wpAmpEditor;

/**
* Adds an 'Enable AMP' toggle to the block editor 'Status & Visibility' section.
*
* If there are error(s) that block AMP from being enabled or disabled,
* this only displays a Notice with the error(s), not a toggle.
* Error(s) are imported as errorMessages via wp_localize_script().
*
* @return {Object} AMPToggle component.
*/
function AMPToggle( { enabledStatus, onAmpChange } ) {
return (
<Fragment>
<PluginPostStatusInfo>
{ ! errorMessages.length && __( 'Enable AMP', 'amp' ) }
{
! errorMessages.length &&
(
<FormToggle
checked={ 'enabled' === enabledStatus }
onChange={ () => onAmpChange( enabledStatus ) }
id={ 'amp-enabled' }
/>
)
}
{
!! errorMessages.length &&
(
<Notice
status={ 'warning' }
isDismissible={ false }
>
{
errorMessages.map( function( message ) {
let minSplitLength = 2;

if ( 'string' === typeof message ) {
// The message is only a string, so return it.
return message;
}
if ( message[ 0 ].split( '%s' ).length > minSplitLength ) {
/**
* The message is an array with the text in the 0 index, and the href in the 1 index.
* And the text should have two %s as placeholders for <a>, like 'AMP cannot be enabled because this %spost type does not support it%s.'.
* So split it along %s, to construct the message with the <a>, like:
* 'AMP cannot be enabled because this <a href="foo">post type does not support it</a>.'.
*/
let splitMessage = message[ 0 ].split( '%s' );
return ( <p>{ splitMessage[ 0 ] }<a href={ message[ 1 ] }>{ splitMessage[ 1 ] }</a>{ splitMessage[ 2 ] }</p> );
}
} )
}
</Notice>
)
}
</PluginPostStatusInfo>
</Fragment>
);
}

/**
* The AMP Toggle component, composed with the enabledStatus and a callback for when it's changed.
*
* @return {Object} The composed AMP toggle.
*/
function ComposedAMPToggle() {
return compose( [
withSelect( ( select ) => {
/**
* Gets the AMP enabled status.
*
* Uses select from the enclosing function to get the meta value.
* If it doesn't exist, it uses the default value.
* This applies especially for a new post, where there probably won't be a meta value yet.
*
* @return {string} Enabled status, either 'enabled' or 'disabled'.
*/
let getEnabledStatus = function() {
let metaSetatus = select( 'core/editor' ).getEditedPostAttribute( 'meta' ).amp_status;
if ( possibleStati.includes( metaSetatus ) ) {
return metaSetatus;
}
return defaultStatus;
};

return { enabledStatus: getEnabledStatus() };
} ),
withDispatch( ( dispatch ) => ( {
onAmpChange: function( enabledStatus ) {
let newStatus = 'enabled' === enabledStatus ? 'disabled' : 'enabled';
dispatch( 'core/editor' ).editPost( { meta: { amp_status: newStatus } } );
}
} ) ),
withInstanceId
] )( AMPToggle );
}

export default wp.plugins.registerPlugin( 'amp', {
icon: 'hidden',
render: ComposedAMPToggle()
} );
175 changes: 169 additions & 6 deletions includes/admin/class-amp-post-meta-box.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ class AMP_Post_Meta_Box {
*/
const ASSETS_HANDLE = 'amp-post-meta-box';

/**
* Block asset handle.
*
* @since 1.0
* @var string
*/
const BLOCK_ASSET_HANDLE = 'amp-block-editor-toggle-compiled';

/**
* The enabled status post meta value.
*
Expand Down Expand Up @@ -84,6 +92,7 @@ public function init() {
) );

add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_assets' ) );
add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_block_assets' ) );
add_action( 'post_submitbox_misc_actions', array( $this, 'render_status' ) );
add_action( 'save_post', array( $this, 'save_amp_status' ) );
add_filter( 'preview_post_link', array( $this, 'preview_post_link' ) );
Expand Down Expand Up @@ -165,6 +174,47 @@ public function enqueue_admin_assets() {
) );
}

/**
* Enqueues block assets.
* The name of gutenberg_get_jed_locale_data() may change, as the Gutenberg Core merge approaches.
*
* @since 1.0
*/
public function enqueue_block_assets() {
$post = get_post();
if ( ! is_post_type_viewable( $post->post_type ) ) {
return;
}

wp_enqueue_script(
self::BLOCK_ASSET_HANDLE,
amp_get_asset_url( 'js/' . self::BLOCK_ASSET_HANDLE . '.js' ),
array( 'wp-hooks', 'wp-i18n', 'wp-components' ),
AMP__VERSION,
true
);

$status_and_errors = $this->get_status_and_errors( $post );
$enabled_status = $status_and_errors['status'];
$error_messages = $this->get_raw_error_messages( $status_and_errors['status'], $status_and_errors['errors'] );
$localization = array(
'possibleStati' => array( self::ENABLED_STATUS, self::DISABLED_STATUS ),
'defaultStatus' => $enabled_status,
'errorMessages' => $error_messages,
);

if ( function_exists( 'gutenberg_get_jed_locale_data' ) ) {
$localization['i18n'] = gutenberg_get_jed_locale_data( 'amp' ); // @todo create a POT file.
}

wp_localize_script(
self::BLOCK_ASSET_HANDLE,
'wpAmpEditor',
$localization
);

}

/**
* Render AMP status.
*
Expand All @@ -184,6 +234,33 @@ public function render_status( $post ) {
return;
}

$status_and_errors = $this->get_status_and_errors( $post );
$status = $status_and_errors['status'];
$errors = $status_and_errors['errors'];
$error_messages = $this->get_classic_editor_error_messages( $this->get_raw_error_messages( $status, $errors ) );

$labels = array(
'enabled' => __( 'Enabled', 'amp' ),
'disabled' => __( 'Disabled', 'amp' ),
);

// The preceding variables are used inside the following amp-status.php template.
include AMP__DIR__ . '/templates/admin/amp-status.php';
}

/**
* Gets the AMP enabled status and errors.
*
* @since 1.0
* @param WP_Post $post The post to check.
* @return array {
* The status and errors.
*
* @type string $status The AMP enabled status.
* @type string[] $errors AMP errors.
* }
*/
public function get_status_and_errors( $post ) {
/*
* When theme support is present then theme templates can be served in AMP and we check first if the template is available.
* Checking for template availability will include a check for get_support_errors. Otherwise, if theme support is not present
Expand All @@ -202,13 +279,99 @@ public function render_status( $post ) {
$errors = array_diff( $errors, array( 'post-status-disabled' ) ); // Subtract the status which the metabox will allow to be toggled.
}

$labels = array(
'enabled' => __( 'Enabled', 'amp' ),
'disabled' => __( 'Disabled', 'amp' ),
);
return compact( 'status', 'errors' );
}

// The preceding variables are used inside the following amp-status.php template.
include AMP__DIR__ . '/templates/admin/amp-status.php';
/**
* Gets the raw AMP enabled error message(s).
*
* When there is an <a> in the message, this does not use sprintf() yet to create a single string message.
* This is because the block editor toggle script has to construct this message with JS,
* and it needs the text of the message to be separate from the URL.
* So when there is an <a>, this adds a %s for the opening <a> and a %s for the closing </a>.
* For example, 'There are no <a href="">supported templates</a> to display this in AMP.'
* is outputs as: 'There are no %ssupported templates%s to display this in AMP.'
* That string is in the 0 index of the array(), and the href is in the 1 index.
*
* @since 1.0
* @param string $status The AMP enabled status.
* @param array $errors The AMP enabled errors.
* @return array $error_messages The error message(s), each message being either a string or an array of strings.
*/
public function get_raw_error_messages( $status, $errors ) {
$error_messages = array();
if ( in_array( 'status_immutable', $errors, true ) ) {
if ( self::ENABLED_STATUS === $status ) {
$error_messages[] = __( 'Your site does not allow AMP to be disabled.', 'amp' );
} else {
$error_messages[] = __( 'Your site does not allow AMP to be enabled.', 'amp' );
}
}
if ( in_array( 'template_unsupported', $errors, true ) || in_array( 'no_matching_template', $errors, true ) ) {
$error_messages[] = array(
/* translators: %s is opening <a> for AMP settings screen, %s is closing </a> */
__( 'There are no %ssupported templates%s to display this in AMP.', 'amp' ), // phpcs:ignore WordPress.WP.I18n.UnorderedPlaceholdersText, WordPress.Arrays.ArrayDeclarationSpacing.ArrayItemNoNewLine
esc_url( admin_url( 'admin.php?page=' . AMP_Options_Manager::OPTION_NAME ) ),
);
}
if ( in_array( 'password-protected', $errors, true ) ) {
$error_messages[] = __( 'AMP cannot be enabled on password protected posts.', 'amp' );
}
if ( in_array( 'post-type-support', $errors, true ) ) {
$error_messages[] = array(
/* translators: %s is opening <a> for AMP settings screen, %s is closing </a> */
__( 'AMP cannot be enabled because this %spost type does not support it%s.', 'amp' ), // phpcs:ignore WordPress.WP.I18n.UnorderedPlaceholdersText, WordPress.Arrays.ArrayDeclarationSpacing.ArrayItemNoNewLine
esc_url( admin_url( 'admin.php?page=' . AMP_Options_Manager::OPTION_NAME ) ),
);
}
if ( in_array( 'skip-post', $errors, true ) ) {
$error_messages[] = __( 'A plugin or theme has disabled AMP support.', 'amp' );
}
if ( count( array_diff( $errors, array( 'status_immutable', 'page-on-front', 'page-for-posts', 'password-protected', 'post-type-support', 'skip-post', 'template_unsupported', 'no_matching_template' ) ) ) > 0 ) {
$error_messages[] = __( 'Unavailable for an unknown reason.', 'amp' );
}

return $error_messages;
}

/**
* Gets the AMP enabled error message(s) for the Classic Editor.
*
* AMP can be disabled for several reasons,
* like if the user disables it in Supported Templates > Content Types.
* Then, it would not make sense to allow the user to enable AMP in the post editor,
* as it's already disabled.
* So this gets the message(s) that should display.
*
* @param array $raw_error_messages The raw error messages, possibly with URLs.
* @return array $error_messages The error message(s), as an array of strings.
*/
public function get_classic_editor_error_messages( $raw_error_messages ) {
Copy link
Member

Choose a reason for hiding this comment

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

There is duplication between PHP here and the JS in PluginPostStatusInfo. I appreciate the concern to not dangerouslySetInnerHTML but since the error messages are coming from a trusted source, I think it would be better to do so in this case. Even in Gutenberg, for example, there is a RawHTML component for this purpose. This component is publicly available already in Gutenberg via wp.element.RawHTML.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, I'm working on this now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

These commits simplify the error messages to use RawHTML like you suggested.

$processed_error_messages = array();
foreach ( $raw_error_messages as $raw_message ) {
if ( is_array( $raw_message ) && isset( $raw_message[0], $raw_message[1] ) ) {
/**
* Processes the error message(s), using sprintf() to create <a> elements.
* $raw_message[0] should have two %s, like 'AMP cannot be enabled because this %spost type does not support it%s'.
* And $raw_message[1] should have the href.
* This happens at this stage so the block editor can also use get_raw_error_messages().
* It has to produce the <a> elements with JS, so it can't receive a single string with the <a> included.
*/
$processed_error_messages[] = wp_kses_post( sprintf(
$raw_message[0],
sprintf(
'<a href="%s">',
$raw_message[1]
),
'</a>'
) );
} elseif ( is_string( $raw_message ) ) {
// The message is only a string without an <a>, so simply add it to the processed error messages.
$processed_error_messages[] = $raw_message;
}
}

return $processed_error_messages;
}

/**
Expand Down
31 changes: 1 addition & 30 deletions templates/admin/amp-status.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,36 +39,7 @@
</fieldset>
<?php else : ?>
<div class="inline notice notice-warning notice-alt">
<p>
<?php
$error_messages = array();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This section is moved to get_raw_error_messages().

if ( in_array( 'status_immutable', $errors, true ) ) {
if ( self::ENABLED_STATUS === $status ) {
$error_messages[] = __( 'Your site does not allow AMP to be disabled.', 'amp' );
} else {
$error_messages[] = __( 'Your site does not allow AMP to be enabled.', 'amp' );
}
}
if ( in_array( 'template_unsupported', $errors, true ) || in_array( 'no_matching_template', $errors, true ) ) {
/* translators: %s is URL to AMP settings screen */
$error_messages[] = wp_kses_post( sprintf( __( 'There are no <a href="%s">supported templates</a> to display this in AMP.', 'amp' ), esc_url( admin_url( 'admin.php?page=' . AMP_Options_Manager::OPTION_NAME ) ) ) );
}
if ( in_array( 'password-protected', $errors, true ) ) {
$error_messages[] = __( 'AMP cannot be enabled on password protected posts.', 'amp' );
}
if ( in_array( 'post-type-support', $errors, true ) ) {
/* translators: %s is URL to AMP settings screen */
$error_messages[] = wp_kses_post( sprintf( __( 'AMP cannot be enabled because this <a href="%s">post type does not support it</a>.', 'amp' ), esc_url( admin_url( 'admin.php?page=' . AMP_Options_Manager::OPTION_NAME ) ) ) );
}
if ( in_array( 'skip-post', $errors, true ) ) {
$error_messages[] = __( 'A plugin or theme has disabled AMP support.', 'amp' );
}
if ( count( array_diff( $errors, array( 'status_immutable', 'page-on-front', 'page-for-posts', 'password-protected', 'post-type-support', 'skip-post', 'template_unsupported', 'no_matching_template' ) ) ) > 0 ) {
$error_messages[] = __( 'Unavailable for an unknown reason.', 'amp' );
}
echo implode( ' ', $error_messages ); // WPCS: xss ok.
?>
</p>
<p><?php echo implode( ' ', $error_messages ); // WPCS: xss ok. ?></p>
</div>
<?php endif; ?>
<div class="amp-status-actions">
Expand Down
Loading