diff --git a/includes/BlockRenderer.php b/includes/BlockRenderer.php index 9dbabb0..6292b71 100644 --- a/includes/BlockRenderer.php +++ b/includes/BlockRenderer.php @@ -17,10 +17,11 @@ class BlockRenderer { */ public static function render( $attributes ) { // Block attributes - $icon_data = $attributes['icon'] ?? []; - $radius = (string) ( $attributes['borderRadius'] ?? '0px' ); - $link_url = (string) ( $attributes['url'] ?? '' ); - $link_label = (string) ( $attributes['label'] ?? '' ); + $icon_data = $attributes['icon'] ?? []; + $radius = (string) ( $attributes['borderRadius'] ?? '0px' ); + $link_url = (string) ( $attributes['url'] ?? '' ); + $link_label = (string) ( $attributes['label'] ?? '' ); + $target_blank = ! empty( $attributes['targetBlank'] ); $collection_name = $icon_data['collection'] ?? null; $icon_name = $icon_data['name'] ?? null; @@ -52,10 +53,17 @@ public static function render( $attributes ) { // Start block link if ( ! empty( $link_url ) ) { + $link_attrs = ''; + if ( ! empty( $link_label ) ) { + $link_attrs .= sprintf( ' aria-label="%s"', esc_attr( $link_label ) ); + } + if ( $target_blank ) { + $link_attrs .= ' target="_blank" rel="noopener noreferrer"'; + } $html .= sprintf( '', esc_url( $link_url ), - ! empty( $link_label ) ? sprintf( ' aria-label="%s"', esc_attr( $link_label ) ) : '' + $link_attrs ); } diff --git a/src/block.json b/src/block.json index d447f27..c2797e3 100644 --- a/src/block.json +++ b/src/block.json @@ -51,6 +51,10 @@ }, "label": { "type": "string" + }, + "targetBlank": { + "type": "boolean", + "default": false } }, "textdomain": "blockparty-icons", diff --git a/src/components/link-url-popover.js b/src/components/link-url-popover.js index 20e24bd..b02cd6d 100644 --- a/src/components/link-url-popover.js +++ b/src/components/link-url-popover.js @@ -9,11 +9,12 @@ import { store as blockEditorStore, } from '@wordpress/block-editor'; import { __ } from '@wordpress/i18n'; -import { Button } from '@wordpress/components'; +import { Button, ToggleControl } from '@wordpress/components'; import { keyboardReturn } from '@wordpress/icons'; export default function SocialLinkURLPopover( { url, + targetBlank, setAttributes, setPopover, popoverAnchor, @@ -25,42 +26,54 @@ export default function SocialLinkURLPopover( { anchor={ popoverAnchor } onClose={ () => setPopover( false ) } > -
{ - event.preventDefault(); - setPopover( false ); - } } - > -
- + { + event.preventDefault(); + setPopover( false ); + } } + > +
+ + setAttributes( { url: nextURL } ) + } + placeholder={ __( 'Enter address' ) } + disableSuggestions={ true } + onKeyDown={ ( event ) => { + if ( + !! url || + event.defaultPrevented || + ! [ BACKSPACE, DELETE ].includes( + event.keyCode + ) + ) { + return; + } + removeBlock( clientId ); + } } + /> +
+
); } diff --git a/src/edit.js b/src/edit.js index 4f36e8d..3984886 100644 --- a/src/edit.js +++ b/src/edit.js @@ -90,7 +90,8 @@ export default function Edit( { isSelected, setAttributes, } ) { - const { borderRadius, iconColor, icon, label, size, url } = attributes; + const { borderRadius, iconColor, icon, label, targetBlank, size, url } = + attributes; const blockRef = useRef( null ); const [ icons, setIcons ] = useState( null ); @@ -107,7 +108,8 @@ export default function Edit( { }; const removeLink = () => { - setAttributes( { url: '' } ); + setPopover( false ); + setAttributes( { url: '', targetBlank: false } ); }; const openIconModal = () => { @@ -233,15 +235,32 @@ export default function Edit( { { icon && ( <> - + { url ? ( + + + + + ) : ( + + ) } @@ -267,6 +286,7 @@ export default function Edit( { { showURLPopover && ( assertStringNotContainsString( 'aria-label', $html ); } + public function test_url_without_target_blank_has_no_target_or_rel(): void { + $html = $this->render( + [ + 'icon' => $this->icon(), + 'url' => 'https://example.org', + ] + ); + + $this->assertStringNotContainsString( 'target=', $html ); + $this->assertStringNotContainsString( 'rel=', $html ); + } + + public function test_target_blank_adds_target_and_rel(): void { + $html = $this->render( + [ + 'icon' => $this->icon(), + 'url' => 'https://example.org', + 'targetBlank' => true, + ] + ); + + $this->assertStringContainsString( 'target="_blank"', $html ); + $this->assertStringContainsString( 'rel="noopener noreferrer"', $html ); + } + + public function test_target_blank_false_leaves_link_unchanged(): void { + $html = $this->render( + [ + 'icon' => $this->icon(), + 'url' => 'https://example.org', + 'targetBlank' => false, + ] + ); + + $this->assertStringContainsString( 'href="https://example.org"', $html ); + $this->assertStringNotContainsString( 'target=', $html ); + $this->assertStringNotContainsString( 'rel=', $html ); + } + + public function test_target_blank_without_url_produces_no_link(): void { + $html = $this->render( + [ + 'icon' => $this->icon(), + 'targetBlank' => true, + ] + ); + + $this->assertStringNotContainsString( '