diff --git a/assets/css/msls.css b/assets/css/msls.css index 85310606..ed42d936 100644 --- a/assets/css/msls.css +++ b/assets/css/msls.css @@ -1 +1 @@ -div#msls.postbox label{margin-right:6px}div#msls.postbox input.msls_title,div#msls.postbox select{width:100%}select.msls-translations{width:226px}#msls.postbox .inside li{display:flex;align-items:center}#msls.postbox .inside li label{display:flex}#msls.postbox .inside li input.msls_title,#msls.postbox .inside li select{flex-grow:1}#msls-content-import .button-primary{margin:1em auto}.flag-icon{width:1.3333em!important;height:1em!important;vertical-align:middle;overflow:hidden;line-height:1!important;color:transparent}.msls-icon-wrapper{display:inline-flex;justify-content:center;align-items:center;text-align:center}.msls-icon-wrapper.flag{min-width:36px}.msls-icon-wrapper.label{min-width:48px}label .msls-icon-wrapper{text-align:left}#wpadminbar * .language-badge,#wpadminbar .language-badge,.language-badge{display:inline-block;min-width:32px;height:auto;padding:4px 6px;white-space:nowrap;font-size:10px;line-height:1;text-align:center;background-color:currentColor;border-radius:9px;user-select:none}#wpadminbar * .language-badge>span,#wpadminbar .language-badge>span,.language-badge>span{display:inline-block;vertical-align:top;margin:0 1px;font-size:10px;font-weight:600;line-height:1;text-transform:uppercase;color:#fff;text-align:center}#wpadminbar * .language-badge>span:nth-child(2),#wpadminbar .language-badge>span:nth-child(2),.language-badge>span:nth-child(2){opacity:.5}.column-mslscol .language-badge{margin:0 1px!important}.column-mslscol{width:56px}#wpadminbar * .language-badge,#wpadminbar .language-badge{position:relative;top:-1px;padding-top:3px;padding-bottom:3px;background-color:transparent;border:1px currentColor solid}#wpadminbar * .language-badge>span,#wpadminbar .language-badge>span{color:currentColor}.msls-quick-create{background:none;border:none;padding:0;margin:0;cursor:pointer;color:inherit;font:inherit;line-height:inherit}.msls-quick-create.msls-loading .dashicons{animation:msls-spin 1s linear infinite}@keyframes msls-spin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}} \ No newline at end of file +div#msls.postbox label{margin-right:6px}div#msls.postbox input.msls_title,div#msls.postbox select{width:100%}select.msls-translations{width:226px}#msls.postbox .inside li{display:flex;align-items:center}#msls.postbox .inside li label{display:flex}#msls.postbox .inside li input.msls_title,#msls.postbox .inside li select{flex-grow:1}#msls.postbox .inside li .msls-create-new,#msls.postbox .inside li .msls-edit-link{text-decoration:none;margin-left:4px;color:#2271b1}#msls.postbox .inside li .msls-create-new:hover,#msls.postbox .inside li .msls-edit-link:hover{color:#135e96}.msls-quick-create{background:0 0;border:none;padding:0;margin:0;cursor:pointer;color:inherit;font:inherit;line-height:inherit}.msls-quick-create.msls-loading .dashicons{animation:msls-spin 1s linear infinite}@keyframes msls-spin{from{transform:rotate(0)}to{transform:rotate(360deg)}}#msls-content-import .button-primary{margin:1em auto}.flag-icon{width:1.3333em!important;height:1em!important;vertical-align:middle;overflow:hidden;line-height:1!important;color:transparent}.msls-icon-wrapper{display:inline-flex;justify-content:center;align-items:center;text-align:center}.msls-icon-wrapper.flag{min-width:36px}.msls-icon-wrapper.label{min-width:48px}label .msls-icon-wrapper{text-align:left}#wpadminbar * .language-badge,#wpadminbar .language-badge,.language-badge{display:inline-block;min-width:32px;height:auto;padding:4px 6px;white-space:nowrap;font-size:10px;line-height:1;text-align:center;background-color:currentColor;border-radius:9px;user-select:none}#wpadminbar * .language-badge>span,#wpadminbar .language-badge>span,.language-badge>span{display:inline-block;vertical-align:top;margin:0 1px;font-size:10px;font-weight:600;line-height:1;text-transform:uppercase;color:#fff;text-align:center}#wpadminbar * .language-badge>span:nth-child(2),#wpadminbar .language-badge>span:nth-child(2),.language-badge>span:nth-child(2){opacity:.5}.column-mslscol .language-badge{margin:0 1px!important}.column-mslscol{width:56px}#wpadminbar * .language-badge,#wpadminbar .language-badge{position:relative;top:-1px;padding-top:3px;padding-bottom:3px;background-color:transparent;border:1px currentColor solid}#wpadminbar * .language-badge>span,#wpadminbar .language-badge>span{color:currentColor} \ No newline at end of file diff --git a/assets/css/msls.less b/assets/css/msls.less index e10339aa..42c32c2b 100644 --- a/assets/css/msls.less +++ b/assets/css/msls.less @@ -24,10 +24,38 @@ select.msls-translations { input.msls_title, select { flex-grow: 1; } + .msls-create-new, + .msls-edit-link { + text-decoration: none; + margin-left: 4px; + color: #2271b1; + &:hover { + color: #135e96; + } + } } } } +.msls-quick-create { + background: none; + border: none; + padding: 0; + margin: 0; + cursor: pointer; + color: inherit; + font: inherit; + line-height: inherit; + &.msls-loading .dashicons { + animation: msls-spin 1s linear infinite; + } +} + +@keyframes msls-spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} + #msls-content-import { .button-primary { margin: 1em auto; diff --git a/includes/MslsMetaBox.php b/includes/MslsMetaBox.php index 3f4f0752..58b0f4a5 100644 --- a/includes/MslsMetaBox.php +++ b/includes/MslsMetaBox.php @@ -180,8 +180,10 @@ public function render_select(): void { if ( $blogs ) { global $post; - $type = get_post_type( $post->ID ); - $mydata = new MslsOptionsPost( $post->ID ); + $type = get_post_type( $post->ID ); + $mydata = new MslsOptionsPost( $post->ID ); + $origin_language = MslsBlogCollection::get_blog_language(); + $is_saved = 'auto-draft' !== get_post_status( $post ); $this->maybe_set_linked_post( $mydata ); @@ -198,8 +200,10 @@ public function render_select(): void { $icon_type = $this->options->get_icon_type(); $icon = MslsAdminIcon::create( $type )->set_language( $language )->set_icon_type( $icon_type ); + $linked_post_id = null; if ( $mydata->has_value( $language ) ) { - $icon->set_href( (int) $mydata->$language ); + $linked_post_id = (int) $mydata->$language; + $icon->set_href( $linked_post_id ); } $selects = ''; @@ -234,11 +238,17 @@ public function render_select(): void { ); } + $action = ''; + if ( $is_saved ) { + $action = $this->get_create_new_link( $type, $language, $post->ID, $origin_language, $linked_post_id ); + } + $lis .= sprintf( - '
  • %3$s
  • ', + '
  • %3$s%4$s
  • ', esc_attr( $language ), $icon, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped $selects, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + $action, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped esc_attr( $icon_type ) ); @@ -311,8 +321,10 @@ public function render_input(): void { if ( $blogs ) { global $post; - $post_type = get_post_type( $post->ID ); - $my_data = new MslsOptionsPost( $post->ID ); + $post_type = get_post_type( $post->ID ); + $my_data = new MslsOptionsPost( $post->ID ); + $origin_language = MslsBlogCollection::get_blog_language(); + $is_saved = 'auto-draft' !== get_post_status( $post ); $this->maybe_set_linked_post( $my_data ); @@ -330,19 +342,27 @@ public function render_input(): void { $value = ''; $title = ''; + $linked_post_id = null; if ( $my_data->has_value( $language ) ) { - $icon->set_href( (int) $my_data->$language ); + $linked_post_id = (int) $my_data->$language; + $icon->set_href( $linked_post_id ); $value = $my_data->$language; $title = get_the_title( $value ); } + $action = ''; + if ( $is_saved ) { + $action = $this->get_create_new_link( $post_type, $language, $post->ID, $origin_language, $linked_post_id ); + } + $items .= sprintf( - '
  • ', + '
  • %6$s
  • ', $blog->userblog_id, $icon, $language, $value, $title, + $action, esc_attr( $icon_type ) ); @@ -372,6 +392,70 @@ public function render_input(): void { } } + /** + * Renders the action element for a language row in the metabox. + * + * Returns a "+" create button (Quick Create or classic link) when no + * translation is linked, or an external-link icon when one exists. + * + * @param string $type Post type slug. + * @param string $language Target language code. + * @param int $post_id Current (source) post ID. + * @param string $origin_language Source blog language code. + * @param ?int $linked_post_id Linked translation post ID, or null. + * + * @return string + */ + private function get_create_new_link( string $type, string $language, int $post_id, string $origin_language, ?int $linked_post_id ): string { + if ( null !== $linked_post_id ) { + $href = (string) get_edit_post_link( $linked_post_id ); + + if ( '' !== $href ) { + $title = sprintf( + /* translators: %s: language code */ + __( 'Edit the translation in the %s-blog', 'multisite-language-switcher' ), + $language + ); + + return sprintf( + '', + esc_url( $href ), + esc_attr( $title ) + ); + } + } + + if ( msls_options()->activate_quick_create ) { + $action_icon = ( new MslsAdminIcon( $type ) ) + ->set_language( $language ) + ->set_icon_type( 'action' ) + ->set_id( $post_id ) + ->set_origin_language( $origin_language ); + + return $action_icon->get_a(); + } + + $action_icon = ( new MslsAdminIcon( $type ) ) + ->set_language( $language ) + ->set_icon_type( 'action' ) + ->set_id( $post_id ) + ->set_origin_language( $origin_language ); + + $href = $action_icon->get_edit_new(); + + $title = sprintf( + /* translators: %s: language code */ + __( 'Create a new translation in the %s-blog', 'multisite-language-switcher' ), + $language + ); + + return sprintf( + '', + esc_url( $href ), + esc_attr( $title ) + ); + } + /** * Set * diff --git a/includes/MslsRestApi.php b/includes/MslsRestApi.php index 17d7002a..d1b5f60b 100644 --- a/includes/MslsRestApi.php +++ b/includes/MslsRestApi.php @@ -156,14 +156,16 @@ public function create_translation( \WP_REST_Request $request ) { */ do_action( 'msls_quick_create_after_insert', $new_post_id, $source_post, $source_blog_id, $target_blog_id ); - $edit_url = get_edit_post_link( $new_post_id, 'raw' ); + $edit_url = get_edit_post_link( $new_post_id, 'raw' ); + $post_title = get_the_title( $new_post_id ); restore_current_blog(); $this->establish_link( $source_post_id, $source_blog_id, $new_post_id, $target_blog_id ); $response_data = array( - 'post_id' => $new_post_id, - 'edit_url' => $edit_url, + 'post_id' => $new_post_id, + 'edit_url' => $edit_url, + 'post_title' => $post_title, ); /** diff --git a/src/msls-quick-create.js b/src/msls-quick-create.js index 36771c71..b2a2debe 100644 --- a/src/msls-quick-create.js +++ b/src/msls-quick-create.js @@ -25,12 +25,18 @@ jQuery( document ).ready( } ).then( function ( response ) { + var isMetabox = $button.closest( '#msls' ).length > 0; var $link = $( '' ) .attr( 'href', response.edit_url ) .attr( 'title', $button.attr( 'title' ).replace( /Create/, 'Edit' ) ) .html( $button.html() ); - $link.find( '.dashicons' ).removeClass( 'dashicons-update dashicons-plus' ).addClass( 'dashicons-edit' ); + if ( isMetabox ) { + $link.addClass( 'msls-edit-link' ).attr( 'target', '_blank' ); + } + + var successIcon = isMetabox ? 'dashicons-external' : 'dashicons-edit'; + $link.find( '.dashicons' ).removeClass( 'dashicons-update dashicons-plus' ).addClass( successIcon ); $button.replaceWith( $link ); @@ -44,10 +50,15 @@ jQuery( document ).ready( $hiddenInput.val( response.post_id ); } + var $titleInput = $container.find( 'input.msls_title' ); + if ( $titleInput.length ) { + $titleInput.val( response.post_title || '' ); + } + var $select = $container.find( 'select[name^="msls_input_"]' ); if ( $select.length ) { $select.append( - $( ''; + $expected = ''; $this->expectOutputString( $expected ); $this->MslsMetaBoxFactory()->render_select(); @@ -234,15 +240,18 @@ public function test_render_select_hierarchical(): void { Functions\expect( 'get_post_type' )->once()->andReturn( 'page' ); Functions\expect( 'get_option' )->once()->andReturn( array( 'de_DE' => 42 ) ); + Functions\expect( 'get_blog_option' )->once()->andReturn( '' ); + Functions\expect( 'get_post_status' )->once()->andReturn( 'draft' ); Functions\expect( 'wp_nonce_field' )->once()->andReturn( 'nonce_field' ); Functions\expect( 'switch_to_blog' )->once(); Functions\expect( 'restore_current_blog' )->once(); Functions\expect( 'add_query_arg' )->once()->andReturn( 'query_args' ); Functions\expect( 'get_post_type_object' )->once()->andReturn( $wp_post_type ); Functions\expect( 'wp_dropdown_pages' )->once()->andReturn( '' ); - Functions\expect( 'get_edit_post_link' )->once()->andReturn( 'edit-post-link' ); + Functions\expect( 'get_edit_post_link' )->twice()->andReturn( 'edit-post-link' ); + Functions\expect( 'get_current_blog_id' )->once()->andReturn( 1 ); - $expected = ''; + $expected = ''; $this->expectOutputString( $expected ); $this->MslsMetaBoxFactory()->render_select(); @@ -250,35 +259,42 @@ public function test_render_select_hierarchical(): void { public static function render_input_provider(): array { return array( - array( array( 'de_DE' => 42 ), 1, 0, 0, 1, '' ), - array( array( 'en_US' => 17 ), 0, 1, 1, 0, '' ), + array( array( 'de_DE' => 42 ), 1, 1, 0, 2, 0, '' ), + array( array( 'en_US' => 17 ), 0, 3, 2, 0, 2, '' ), ); } /** * @dataProvider render_input_provider */ - public function test_render_input( $option, $the_title_times, $current_blog_id_times, $admin_url_times, $edit_post_link_times, $expected ) { + public function test_render_input( $option, $the_title_times, $current_blog_id_times, $admin_url_times, $edit_post_link_times, $add_query_arg_times, $expected ) { global $post; $post = \Mockery::mock( 'WP_Post' ); $post->ID = 42; + $options = \Mockery::mock( MslsOptions::class ); + $options->activate_quick_create = false; + $post_type = \Mockery::mock( MslsPostType::class ); $post_type->shouldReceive( 'is_taxonomy' )->once()->andReturnFalse(); $post_type->shouldReceive( 'get_request' )->once()->andReturn( 'post' ); Functions\expect( 'msls_content_types' )->once()->andReturn( $post_type ); + Functions\expect( 'msls_options' )->atMost()->times( 1 )->andReturn( $options ); Functions\expect( 'switch_to_blog' )->once(); Functions\expect( 'restore_current_blog' )->once(); Functions\expect( 'get_post_type' )->once()->andReturn( 'page' ); Functions\expect( 'get_option' )->once()->andReturn( $option ); + Functions\expect( 'get_blog_option' )->once()->andReturn( '' ); + Functions\expect( 'get_post_status' )->once()->andReturn( 'draft' ); Functions\expect( 'wp_nonce_field' )->once()->andReturn( 'nonce_field' ); Functions\expect( 'get_the_title' )->times( $the_title_times )->andReturn( 'Test' ); Functions\expect( 'get_current_blog_id' )->times( $current_blog_id_times )->andReturn( 1 ); Functions\expect( 'get_admin_url' )->times( $admin_url_times )->andReturn( 'admin-url-empty' ); Functions\expect( 'get_edit_post_link' )->times( $edit_post_link_times )->andReturn( 'edit-post-link' ); + Functions\expect( 'add_query_arg' )->times( $add_query_arg_times )->andReturn( 'query_args' ); $this->expectOutputString( $expected ); diff --git a/tests/phpunit/TestMslsRestApi.php b/tests/phpunit/TestMslsRestApi.php index 0b195ada..7f0f1a4c 100644 --- a/tests/phpunit/TestMslsRestApi.php +++ b/tests/phpunit/TestMslsRestApi.php @@ -107,6 +107,7 @@ public function test_create_translation_success(): void { Functions\expect( 'post_type_exists' )->once()->with( 'post' )->andReturn( true ); Functions\expect( 'wp_insert_post' )->once()->andReturn( 42 ); Functions\expect( 'get_edit_post_link' )->once()->with( 42, 'raw' )->andReturn( 'https://example.tld/wp-admin/post.php?post=42&action=edit' ); + Functions\expect( 'get_the_title' )->once()->with( 42 )->andReturn( 'Test Title' ); Functions\expect( 'get_option' )->andReturn( array() ); Functions\expect( 'add_option' )->andReturn( true ); @@ -135,6 +136,7 @@ function ( $hook, $value ) { $data = $result->get_data(); $this->assertEquals( 42, $data['post_id'] ); $this->assertEquals( 'https://example.tld/wp-admin/post.php?post=42&action=edit', $data['edit_url'] ); + $this->assertEquals( 'Test Title', $data['post_title'] ); } public function test_prefix_source_language(): void {