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..29730d22 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,68 @@ 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 ); + + /* translators: %s: language code */ + $title = sprintf( + __( '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(); + + /* translators: %s: language code */ + $title = sprintf( + __( 'Create a new translation in the %s-blog', 'multisite-language-switcher' ), + $language + ); + + return sprintf( + '', + esc_url( $href ), + esc_attr( $title ) + ); + } + /** * Set * diff --git a/src/msls-quick-create.js b/src/msls-quick-create.js index 36771c71..62ff10fe 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 ); diff --git a/src/msls.js b/src/msls.js index e3275abb..75c24577 100644 --- a/src/msls.js +++ b/src/msls.js @@ -37,11 +37,15 @@ jQuery( document ).ready( select: function ( event, ui ) { $( event.target ).val( ui.item.label ); hid_field.val( ui.item.value ); + $( event.target ).siblings( '.msls-create-new, .msls-quick-create' ).hide(); + $( event.target ).siblings( '.msls-edit-link' ).show(); return false; }, change: function ( event, ui ) { if ( ! $( event.target ).val() ) { hid_field.val( '' ); + $( event.target ).siblings( '.msls-create-new, .msls-quick-create' ).show(); + $( event.target ).siblings( '.msls-edit-link' ).hide(); } else if ( mslsinput.id === hid_field.val() && mslsinput.title !== $( event.target ).val() diff --git a/tests/phpunit/TestMslsMetaBox.php b/tests/phpunit/TestMslsMetaBox.php index cc26e67e..d58acbbe 100644 --- a/tests/phpunit/TestMslsMetaBox.php +++ b/tests/phpunit/TestMslsMetaBox.php @@ -192,27 +192,33 @@ public function test_render_select_not_hierarchical(): void { $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(); Functions\expect( 'msls_content_types' )->once()->andReturn( $post_type ); + Functions\expect( 'msls_options' )->once()->andReturn( $options ); $wp_post_type = \Mockery::mock( \WP_Post_Type::class ); $wp_post_type->hierarchical = false; Functions\expect( 'get_post_type' )->once()->andReturn( 'page' ); Functions\expect( 'get_option' )->once()->andReturn( array() ); + 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( 'add_query_arg' )->times( 3 )->andReturn( 'query_args' ); Functions\expect( 'get_post_type_object' )->once()->andReturn( $wp_post_type ); Functions\expect( 'get_post_stati' )->once()->andReturn( array( 'draft', 'public', 'private' ) ); Functions\expect( 'get_posts' )->once()->andReturn( array() ); - Functions\expect( 'get_current_blog_id' )->once()->andReturn( 1 ); - Functions\expect( 'get_admin_url' )->once()->andReturn( 'admin-url-empty' ); + Functions\expect( 'get_current_blog_id' )->times( 3 )->andReturn( 1 ); + Functions\expect( 'get_admin_url' )->twice()->andReturn( 'admin-url-empty' ); - $expected = ''; + $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 );