-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
Social Icon: Try auto-matching variation based on URL #59303
base: trunk
Are you sure you want to change the base?
Changes from all commits
d0070f0
0488a5d
db29dfd
e153792
6c2b7a7
142a9a7
567c762
f923784
7801994
4b67c7d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,43 +25,64 @@ import { | |
} from '@wordpress/components'; | ||
import { __ } from '@wordpress/i18n'; | ||
import { keyboardReturn } from '@wordpress/icons'; | ||
import { isEmail, prependHTTPS } from '@wordpress/url'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import { getIconBySite, getNameBySite } from './social-list'; | ||
import { | ||
getIconBySite, | ||
getNameBySite, | ||
getMatchingService, | ||
} from './social-list'; | ||
|
||
import isURLLike from './is-url-like'; | ||
|
||
const SocialLinkURLPopover = ( { | ||
url, | ||
setAttributes, | ||
setPopover, | ||
onClose, | ||
popoverAnchor, | ||
clientId, | ||
} ) => { | ||
const { removeBlock } = useDispatch( blockEditorStore ); | ||
return ( | ||
<URLPopover | ||
anchor={ popoverAnchor } | ||
aria-label={ __( 'Edit social link' ) } | ||
onClose={ () => { | ||
setPopover( false ); | ||
popoverAnchor?.focus(); | ||
} } | ||
> | ||
<URLPopover anchor={ popoverAnchor } onClose={ () => onClose( false ) }> | ||
<form | ||
className="block-editor-url-popover__link-editor" | ||
onSubmit={ ( event ) => { | ||
event.preventDefault(); | ||
setPopover( false ); | ||
|
||
if ( isURLLike( url ) ) { | ||
// Append https if user did not include it. | ||
setAttributes( { url: prependHTTPS( url ) } ); | ||
} | ||
popoverAnchor?.focus(); | ||
onClose( false ); | ||
} } | ||
> | ||
<div className="block-editor-url-input"> | ||
<URLInput | ||
value={ url } | ||
onChange={ ( nextURL ) => | ||
setAttributes( { url: nextURL } ) | ||
} | ||
onChange={ ( nextURL ) => { | ||
const nextAttributes = { | ||
url: nextURL, | ||
service: undefined, | ||
}; | ||
|
||
if ( isURLLike( nextURL ) || isEmail( nextURL ) ) { | ||
const matchingService = isEmail( nextURL ) | ||
? 'mail' | ||
: getMatchingService( | ||
prependHTTPS( nextURL ) | ||
); | ||
|
||
nextAttributes.service = | ||
matchingService ?? 'chain'; | ||
} | ||
|
||
setAttributes( nextAttributes ); | ||
} } | ||
placeholder={ __( 'Enter social link' ) } | ||
label={ __( 'Enter social link' ) } | ||
hideLabelFromVision | ||
|
@@ -107,14 +128,15 @@ const SocialLinkEdit = ( { | |
iconBackgroundColor, | ||
iconBackgroundColorValue, | ||
} = context; | ||
const [ showURLPopover, setPopover ] = useState( false ); | ||
const classes = clsx( 'wp-social-link', 'wp-social-link-' + service, { | ||
const classes = clsx( 'wp-social-link', { | ||
[ `wp-social-link-${ service }` ]: !! service, | ||
'wp-social-link__is-incomplete': ! url, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The way this 'is-incomplete' CSS class works should be adjusted. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we can remove this class. With update flow, the child block is either a placeholder or matches a service. What do you think? |
||
[ `has-${ iconColor }-color` ]: iconColor, | ||
[ `has-${ iconBackgroundColor }-background-color` ]: | ||
iconBackgroundColor, | ||
} ); | ||
|
||
const [ showPopover, setShowPopover ] = useState( ! url && ! service ); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The inital state change allows Popover to be opened for freshly inserted blocks. |
||
// Use internal state instead of a ref to make sure that the component | ||
// re-renders when the popover's anchor updates. | ||
const [ popoverAnchor, setPopoverAnchor ] = useState( null ); | ||
|
@@ -169,7 +191,7 @@ const SocialLinkEdit = ( { | |
<button | ||
className="wp-block-social-link-anchor" | ||
ref={ setPopoverAnchor } | ||
onClick={ () => setPopover( true ) } | ||
onClick={ () => setShowPopover( true ) } | ||
aria-haspopup="dialog" | ||
> | ||
<IconComponent /> | ||
|
@@ -181,11 +203,11 @@ const SocialLinkEdit = ( { | |
{ socialLinkText } | ||
</span> | ||
</button> | ||
{ isSelected && showURLPopover && ( | ||
{ isSelected && showPopover && ( | ||
<SocialLinkURLPopover | ||
url={ url } | ||
setAttributes={ setAttributes } | ||
setPopover={ setPopover } | ||
onClose={ setShowPopover } | ||
popoverAnchor={ popoverAnchor } | ||
clientId={ clientId } | ||
/> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { getProtocol, isValidProtocol } from '@wordpress/url'; | ||
|
||
// Please see packages/block-editor/src/components/link-control/is-url-like.js | ||
export default function isURLLike( val ) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we decide to ship this PR, I'm going to raise a PR to get this moved into the |
||
const hasSpaces = val.includes( ' ' ); | ||
|
||
if ( hasSpaces ) { | ||
return false; | ||
} | ||
|
||
const protocol = getProtocol( val ); | ||
const protocolIsValid = isValidProtocol( protocol ); | ||
|
||
const mayBeTLD = hasPossibleTLD( val ); | ||
|
||
const isWWW = val?.startsWith( 'www.' ); | ||
|
||
return protocolIsValid || isWWW || mayBeTLD; | ||
} | ||
|
||
// Please see packages/block-editor/src/components/link-control/is-url-like.js | ||
function hasPossibleTLD( url, maxLength = 6 ) { | ||
// Clean the URL by removing anything after the first occurrence of "?" or "#". | ||
const cleanedURL = url.split( /[?#]/ )[ 0 ]; | ||
|
||
// Regular expression explanation: | ||
// - (?<=\S) : Positive lookbehind assertion to ensure there is at least one non-whitespace character before the TLD | ||
// - \. : Matches a literal dot (.) | ||
// - [a-zA-Z_]{2,maxLength} : Matches 2 to maxLength letters or underscores, representing the TLD | ||
// - (?:\/|$) : Non-capturing group that matches either a forward slash (/) or the end of the string | ||
const regex = new RegExp( | ||
`(?<=\\S)\\.(?:[a-zA-Z_]{2,${ maxLength }})(?:\\/|$)` | ||
); | ||
|
||
return regex.test( cleanedURL ); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
/** | ||
* Internal dependencies | ||
*/ | ||
import { getMatchingService } from '../social-list'; | ||
|
||
const fixtures = [ | ||
[ 'https://profiles.wordpress.org/exampleuser', 'wordpress' ], | ||
[ 'https://exampleuser.wordpress.com/', 'wordpress' ], | ||
[ 'https://www.instagram.com/exampleuser/', 'instagram' ], | ||
[ 'https://www.tiktok.com/@exampleuser', 'tiktok' ], | ||
[ 'https://twitter.com/exampleuser', 'x' ], | ||
[ 'https://x.com/exampleuser', 'x' ], | ||
[ 'https://www.facebook.com/exampleuser', 'facebook' ], | ||
[ 'https://www.linkedin.com/in/exampleuser', 'linkedin' ], | ||
[ 'https://www.pinterest.com/exampleuser', 'pinterest' ], | ||
[ 'https://www.snapchat.com/add/exampleuser', 'snapchat' ], | ||
[ 'https://exampleuser.tumblr.com/', 'tumblr' ], | ||
[ 'https://www.reddit.com/user/exampleuser', 'reddit' ], | ||
[ 'https://www.goodreads.com/user/show/12345678-exampleuser', 'goodreads' ], | ||
[ 'https://exampleuser.deviantart.com/', 'deviantart' ], | ||
[ 'https://www.flickr.com/photos/exampleuser', 'flickr' ], | ||
[ 'https://www.behance.net/exampleuser', 'behance' ], | ||
[ 'https://soundcloud.com/exampleuser', 'soundcloud' ], | ||
[ 'https://www.twitch.tv/exampleuser', 'twitch' ], | ||
[ 'https://www.youtube.com/user/exampleuser', 'youtube' ], | ||
[ 'https://vimeo.com/exampleuser', 'vimeo' ], | ||
[ 'https://www.amazon.com/gp/profile/exampleuser', 'amazon' ], | ||
[ 'https://www.amazon.de/gp/profile/exampleuser', 'amazon' ], | ||
[ 'https://www.etsy.com/shop/ExampleShop', 'etsy' ], | ||
[ 'https://www.etsy.com/people/exampleuser', 'etsy' ], | ||
[ 'https://exampleuser.bandcamp.com/', 'bandcamp' ], | ||
[ 'https://dribbble.com/exampleuser', 'dribbble' ], | ||
[ 'https://www.dropbox.com/home/Example_User', 'dropbox' ], | ||
[ 'https://codepen.io/exampleuser', 'codepen' ], | ||
[ 'https://www.yelp.com/user_details?userid=exampleuser', 'yelp' ], | ||
[ 'https://vk.com/id12345678', 'vk' ], | ||
[ 'https://t.me/exampleuser', 'telegram' ], | ||
[ 'https://open.spotify.com/user/exampleuser', 'spotify' ], | ||
[ 'https://getpocket.com/@exampleuser', 'pocket' ], | ||
[ 'https://www.patreon.com/exampleuser', 'patreon' ], | ||
[ 'https://medium.com/@exampleuser', 'medium' ], | ||
[ 'https://www.meetup.com/members/12345678/', 'meetup' ], | ||
[ 'https://github.com/exampleuser', 'github' ], | ||
[ 'https://www.gravatar.com/avatar/{example_hash}', 'gravatar' ], | ||
[ 'https://www.last.fm/user/exampleuser', 'lastfm' ], | ||
[ 'https://foursquare.com/user/exampleuser', 'foursquare' ], | ||
[ 'https://join.skype.com/invite/exampleuser', 'skype' ], | ||
[ 'https://www.threads.net/@exampleuser', 'threads' ], | ||
[ 'https://example.com/feed/', 'feed' ], | ||
[ 'https://500px.com/p/exampleuser?view=photos', 'fivehundredpx' ], | ||
]; | ||
|
||
describe( 'Utils', () => { | ||
describe( 'getMatchingService', () => { | ||
it.each( fixtures )( | ||
'should return the matching service for %s', | ||
( url, expected ) => { | ||
expect( getMatchingService( url ) ).toBe( expected ); | ||
} | ||
); | ||
} ); | ||
} ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we should provide a hit below the URL input for this new feature. Not sure about the design or text thought 😅