diff --git a/amp.php b/amp.php index de6f6507131..9521b6e3620 100644 --- a/amp.php +++ b/amp.php @@ -154,6 +154,7 @@ function amp_init() { add_action( 'wp_loaded', 'amp_post_meta_box' ); add_action( 'wp_loaded', 'amp_editor_core_blocks' ); add_action( 'wp_loaded', 'amp_add_options_menu' ); + add_action( 'wp_loaded', 'amp_admin_pointer' ); add_action( 'parse_query', 'amp_correct_query_when_is_front_page' ); // Redirect the old url of amp page to the updated url. diff --git a/assets/js/amp-admin-pointer.js b/assets/js/amp-admin-pointer.js new file mode 100644 index 00000000000..ed47ff9f5bd --- /dev/null +++ b/assets/js/amp-admin-pointer.js @@ -0,0 +1,47 @@ +/** + * Adds an admin pointer that describes new features in 1.0. + */ + +/* exported ampAdminPointer */ +/* global ajaxurl, jQuery */ +var ampAdminPointer = ( function( $ ) { // eslint-disable-line no-unused-vars + 'use strict'; + + return { + + /** + * Loads the pointer. + * + * @param {Object} data - Module data. + * @return {void} + */ + load: function load( data ) { + var options = $.extend( + data.pointer.options, + { + /** + * Makes a POST request to store the pointer ID as dismissed for this user. + */ + close: function() { + $.post( ajaxurl, { + pointer: data.pointer.pointer_id, + action: 'dismiss-wp-pointer' + } ); + }, + + /** + * Adds styling to the pointer, to ensure it appears alongside the AMP menu. + * + * @param {Object} event The pointer event. + * @param {Object} t Pointer element and state. + */ + show: function( event, t ) { + t.pointer.css( 'position', 'fixed' ); + } + } + ); + + $( data.pointer.target ).pointer( options ).pointer( 'open' ); + } + }; +}( jQuery ) ); diff --git a/includes/admin/class-amp-admin-pointer.php b/includes/admin/class-amp-admin-pointer.php new file mode 100644 index 00000000000..6bceb6b2a92 --- /dev/null +++ b/includes/admin/class-amp-admin-pointer.php @@ -0,0 +1,113 @@ +is_pointer_dismissed() ) { + return; + } + + wp_enqueue_style( 'wp-pointer' ); + + wp_enqueue_script( + self::SCRIPT_SLUG, + amp_get_asset_url( 'js/' . self::SCRIPT_SLUG . '.js' ), + array( 'jquery', 'wp-pointer' ), + AMP__VERSION, + true + ); + + wp_add_inline_script( + self::SCRIPT_SLUG, + sprintf( 'ampAdminPointer.load( %s );', wp_json_encode( $this->get_pointer_data() ) ) + ); + } + + /** + * Whether the AMP admin pointer has been dismissed. + * + * @since 1.0 + * @return boolean Is dismissed. + */ + protected function is_pointer_dismissed() { + $dismissed = get_user_meta( get_current_user_id(), 'dismissed_wp_pointers', true ); + if ( empty( $dismissed ) ) { + return false; + } + $dismissed = explode( ',', strval( $dismissed ) ); + + return in_array( self::TEMPLATE_POINTER_ID, $dismissed, true ); + } + + /** + * Gets the pointer data to pass to the script. + * + * @since 1.0 + * @return array Pointer data. + */ + public function get_pointer_data() { + return array( + 'pointer' => array( + 'pointer_id' => self::TEMPLATE_POINTER_ID, + 'target' => '#toplevel_page_amp-options', + 'options' => array( + 'content' => sprintf( + '

%s

%s

%s

', + __( 'AMP', 'amp' ), + __( 'New AMP Template Modes', 'amp' ), + __( 'You can now reuse your theme\'s templates and styles in AMP responses, in both “Paired” and “Native” modes.', 'amp' ) + ), + 'position' => array( + 'edge' => 'left', + 'align' => 'middle', + ), + ), + ), + ); + } +} diff --git a/includes/admin/functions.php b/includes/admin/functions.php index c2c26d8893c..decaeb3e72b 100644 --- a/includes/admin/functions.php +++ b/includes/admin/functions.php @@ -166,3 +166,13 @@ function amp_editor_core_blocks() { $editor_blocks = new AMP_Editor_Blocks(); $editor_blocks->init(); } + +/** + * Bootstrap the AMP admin pointer class. + * + * @since 1.0 + */ +function amp_admin_pointer() { + $admin_pointer = new AMP_Admin_Pointer(); + $admin_pointer->init(); +} diff --git a/includes/class-amp-autoloader.php b/includes/class-amp-autoloader.php index 1a83f70f3d7..04499b7e6b5 100644 --- a/includes/class-amp-autoloader.php +++ b/includes/class-amp-autoloader.php @@ -35,6 +35,7 @@ class AMP_Autoloader { 'AMP_Comment_Walker' => 'includes/class-amp-comment-walker', 'AMP_Template_Customizer' => 'includes/admin/class-amp-customizer', 'AMP_Post_Meta_Box' => 'includes/admin/class-amp-post-meta-box', + 'AMP_Admin_Pointer' => 'includes/admin/class-amp-admin-pointer', 'AMP_Post_Type_Support' => 'includes/class-amp-post-type-support', 'AMP_Base_Embed_Handler' => 'includes/embeds/class-amp-base-embed-handler', 'AMP_DailyMotion_Embed_Handler' => 'includes/embeds/class-amp-dailymotion-embed', diff --git a/tests/test-class-amp-admin-pointer.php b/tests/test-class-amp-admin-pointer.php new file mode 100644 index 00000000000..8d2b90e8dd7 --- /dev/null +++ b/tests/test-class-amp-admin-pointer.php @@ -0,0 +1,114 @@ +instance = new AMP_Admin_Pointer(); + } + + /** + * Test init. + * + * @covers AMP_Admin_Pointer::init() + */ + public function test_init() { + $this->instance->init(); + $this->assertEquals( 10, has_action( 'admin_enqueue_scripts', array( $this->instance, 'enqueue_pointer' ) ) ); + } + + /** + * Test enqueue_pointer. + * + * @covers AMP_Admin_Pointer::enqueue_pointer() + */ + public function test_enqueue_pointer() { + $user_id = $this->factory()->user->create(); + $pointer_script_slug = 'wp-pointer'; + wp_set_current_user( $user_id ); + + // This pointer isn't in the meta value of dismissed pointers, so the method should enqueue the assets. + update_user_meta( $user_id, self::DISMISSED_KEY, 'foo-pointer' ); + $this->instance->enqueue_pointer(); + $script = wp_scripts()->registered[ AMP_Admin_Pointer::SCRIPT_SLUG ]; + + $this->assertTrue( wp_style_is( $pointer_script_slug ) ); + $this->assertTrue( wp_script_is( AMP_Admin_Pointer::SCRIPT_SLUG ) ); + $this->assertEquals( array( 'jquery', 'wp-pointer' ), $script->deps ); + $this->assertEquals( AMP_Admin_Pointer::SCRIPT_SLUG, $script->handle ); + $this->assertEquals( amp_get_asset_url( 'js/amp-admin-pointer.js' ), $script->src ); + $this->assertEquals( AMP__VERSION, $script->ver ); + $this->assertContains( 'ampAdminPointer.load(', $script->extra['after'][1] ); + } + + /** + * Test is_pointer_dismissed. + * + * @covers AMP_Admin_Pointer::is_pointer_dismissed() + */ + public function test_is_pointer_dismissed() { + $user_id = $this->factory()->user->create(); + wp_set_current_user( $user_id ); + $method = new ReflectionMethod( 'AMP_Admin_Pointer', 'is_pointer_dismissed' ); + $method->setAccessible( true ); + + // When this pointer is in the meta value of dismissed pointers, this should be true. + update_user_meta( $user_id, self::DISMISSED_KEY, AMP_Admin_Pointer::TEMPLATE_POINTER_ID ); + $this->instance->enqueue_pointer(); + $this->assertTrue( $method->invoke( $this->instance ) ); + + // When this pointer isn't in the meta value of dismissed pointers, this should be false. + update_user_meta( $user_id, self::DISMISSED_KEY, 'foo-pointer' ); + $this->assertFalse( $method->invoke( $this->instance ) ); + } + + /** + * Test get_pointer_data. + * + * @covers AMP_Admin_Pointer::get_pointer_data() + */ + public function test_get_pointer_data() { + $pointer_data = $this->instance->get_pointer_data(); + $pointer = $pointer_data['pointer']; + $this->assertContains( '

AMP

New AMP Template Modes

', $pointer['options']['content'] ); + $this->assertEquals( + array( + 'align' => 'middle', + 'edge' => 'left', + ), + $pointer['options']['position'] + ); + $this->assertEquals( AMP_Admin_Pointer::TEMPLATE_POINTER_ID, $pointer['pointer_id'] ); + $this->assertEquals( '#toplevel_page_amp-options', $pointer['target'] ); + } +}