Skip to content
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

Try: add Gravatar hovercards to JP comments #39525

Open
wants to merge 3 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: experiment
Type: type

Try Gravatar hovercards in verbum comments.
3 changes: 2 additions & 1 deletion projects/packages/jetpack-mu-wpcom/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,9 @@
"@automattic/i18n-utils": "1.2.3",
"@automattic/jetpack-base-styles": "workspace:*",
"@automattic/jetpack-shared-extension-utils": "workspace:*",
"@automattic/typography": "1.0.0",
"@automattic/page-pattern-modal": "1.1.5",
"@automattic/typography": "1.0.0",
"@gravatar-com/hovercards": "0.9.2",
"@popperjs/core": "^2.11.8",
"@preact/signals": "^1.2.2",
"@sentry/browser": "7.80.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,69 +192,73 @@ public function enqueue_assets() {
'Log in or provide your name and email to leave a reply.' => __( 'Log in or provide your name and email to leave a reply.', 'jetpack-mu-wpcom' ),
'Log in or provide your name and email to leave a comment.' => __( 'Log in or provide your name and email to leave a comment.', 'jetpack-mu-wpcom' ),
'Receive web and mobile notifications for posts on this site.' => __( 'Receive web and mobile notifications for posts on this site.', 'jetpack-mu-wpcom' ),
'Name' => __( 'Name', 'jetpack-mu-wpcom' ),
'Email (address never made public)' => __( 'Email (address never made public)', 'jetpack-mu-wpcom' ),
'Website (optional)' => __( 'Website (optional)', 'jetpack-mu-wpcom' ),
'Leave a reply. (log in optional)' => __( 'Leave a reply. (log in optional)', 'jetpack-mu-wpcom' ),
'Leave a comment. (log in optional)' => __( 'Leave a comment. (log in optional)', 'jetpack-mu-wpcom' ),
'Log in to leave a reply.' => __( 'Log in to leave a reply.', 'jetpack-mu-wpcom' ),
'Log in to leave a comment.' => __( 'Log in to leave a comment.', 'jetpack-mu-wpcom' ),
'Name' => __( 'Name', 'jetpack-mu-wpcom' ),
'Email (address never made public)' => __( 'Email (address never made public)', 'jetpack-mu-wpcom' ),
'Website (optional)' => __( 'Website (optional)', 'jetpack-mu-wpcom' ),
'Leave a reply. (log in optional)' => __( 'Leave a reply. (log in optional)', 'jetpack-mu-wpcom' ),
'Leave a comment. (log in optional)' => __( 'Leave a comment. (log in optional)', 'jetpack-mu-wpcom' ),
'Log in to leave a reply.' => __( 'Log in to leave a reply.', 'jetpack-mu-wpcom' ),
'Log in to leave a comment.' => __( 'Log in to leave a comment.', 'jetpack-mu-wpcom' ),
/* translators: %s is the name of the provider (WordPress, Facebook, Twitter) */
'Logged in via %s' => __( 'Logged in via %s', 'jetpack-mu-wpcom' ),
'Log out' => __( 'Log out', 'jetpack-mu-wpcom' ),
'Email' => __( 'Email', 'jetpack-mu-wpcom' ),
'(Address never made public)' => __( '(Address never made public)', 'jetpack-mu-wpcom'), // phpcs:ignore PEAR.Functions.FunctionCallSignature.SpaceBeforeCloseBracket
'Instantly' => __( 'Instantly', 'jetpack-mu-wpcom' ),
'Daily' => __( 'Daily', 'jetpack-mu-wpcom' ),
'Reply' => __( 'Reply', 'jetpack-mu-wpcom' ),
'Comment' => __( 'Comment', 'jetpack-mu-wpcom' ),
'WordPress' => __( 'WordPress', 'jetpack-mu-wpcom' ),
'Weekly' => __( 'Weekly', 'jetpack-mu-wpcom' ),
'Notify me of new posts' => __( 'Notify me of new posts', 'jetpack-mu-wpcom' ),
'Email me new posts' => __( 'Email me new posts', 'jetpack-mu-wpcom' ),
'Email me new comments' => __( 'Email me new comments', 'jetpack-mu-wpcom' ),
'Cancel' => __( 'Cancel', 'jetpack-mu-wpcom' ),
'Write a comment...' => __( 'Write a comment...', 'jetpack-mu-wpcom' ),
'Write a reply...' => __( 'Write a reply...', 'jetpack-mu-wpcom' ),
'Website' => __( 'Website', 'jetpack-mu-wpcom' ),
'Optional' => __( 'Optional', 'jetpack-mu-wpcom' ),
'Logged in via %s' => __( 'Logged in via %s', 'jetpack-mu-wpcom' ),
'Log out' => __( 'Log out', 'jetpack-mu-wpcom' ),
'Email' => __( 'Email', 'jetpack-mu-wpcom' ),
'(Address never made public)' => __( '(Address never made public)', 'jetpack-mu-wpcom'), // phpcs:ignore PEAR.Functions.FunctionCallSignature.SpaceBeforeCloseBracket
'Instantly' => __( 'Instantly', 'jetpack-mu-wpcom' ),
'Daily' => __( 'Daily', 'jetpack-mu-wpcom' ),
'Reply' => __( 'Reply', 'jetpack-mu-wpcom' ),
'Comment' => __( 'Comment', 'jetpack-mu-wpcom' ),
'WordPress' => __( 'WordPress', 'jetpack-mu-wpcom' ),
'Weekly' => __( 'Weekly', 'jetpack-mu-wpcom' ),
'Notify me of new posts' => __( 'Notify me of new posts', 'jetpack-mu-wpcom' ),
'Email me new posts' => __( 'Email me new posts', 'jetpack-mu-wpcom' ),
'Email me new comments' => __( 'Email me new comments', 'jetpack-mu-wpcom' ),
'Cancel' => __( 'Cancel', 'jetpack-mu-wpcom' ),
'Write a comment...' => __( 'Write a comment...', 'jetpack-mu-wpcom' ),
'Write a reply...' => __( 'Write a reply...', 'jetpack-mu-wpcom' ),
'Website' => __( 'Website', 'jetpack-mu-wpcom' ),
'Optional' => __( 'Optional', 'jetpack-mu-wpcom' ),
/* translators: Success message of a modal when user subscribes */
'We\'ll keep you in the loop!' => __( 'We\'ll keep you in the loop!', 'jetpack-mu-wpcom' ),
'Loading your comment...' => __( 'Loading your comment...', 'jetpack-mu-wpcom' ),
'We\'ll keep you in the loop!' => __( 'We\'ll keep you in the loop!', 'jetpack-mu-wpcom' ),
'Loading your comment...' => __( 'Loading your comment...', 'jetpack-mu-wpcom' ),
/* translators: %s is the name of the site */
'Discover more from' => sprintf( __( 'Discover more from %s', 'jetpack-mu-wpcom' ), get_bloginfo( 'name' ) ),
'Discover more from' => sprintf( __( 'Discover more from %s', 'jetpack-mu-wpcom' ), get_bloginfo( 'name' ) ),
'Subscribe now to keep reading and get access to the full archive.' => __( 'Subscribe now to keep reading and get access to the full archive.', 'jetpack-mu-wpcom' ),
'Continue reading' => __( 'Continue reading', 'jetpack-mu-wpcom' ),
'Never miss a beat!' => __( 'Never miss a beat!', 'jetpack-mu-wpcom' ),
'Continue reading' => __( 'Continue reading', 'jetpack-mu-wpcom' ),
'Never miss a beat!' => __( 'Never miss a beat!', 'jetpack-mu-wpcom' ),
'Interested in getting blog post updates? Simply click the button below to stay in the loop!' => __( 'Interested in getting blog post updates? Simply click the button below to stay in the loop!', 'jetpack-mu-wpcom' ),
'Enter your email address' => __( 'Enter your email address', 'jetpack-mu-wpcom' ),
'Subscribe' => __( 'Subscribe', 'jetpack-mu-wpcom' ),
'Comment sent successfully' => __( 'Comment sent successfully', 'jetpack-mu-wpcom' ),
'Enter your email address' => __( 'Enter your email address', 'jetpack-mu-wpcom' ),
'Subscribe' => __( 'Subscribe', 'jetpack-mu-wpcom' ),
'Comment sent successfully' => __( 'Comment sent successfully', 'jetpack-mu-wpcom' ),
'This site uses Gravatar to manage avatars and profiles, but we can’t find an account for this email address.' => __( 'This site uses Gravatar to manage avatars and profiles, but we can’t find an account for this email address.', 'jetpack-mu-wpcom' ),
'Is this you? Claim your free profile' => __( 'Is this you? Claim your free profile', 'jetpack-mu-wpcom' ),
'View profile' => __( 'View profile', 'jetpack-mu-wpcom' ),
'Unknown User' => __( 'Unknown User', 'jetpack-mu-wpcom' ),
'Save my name, email, and website in this browser for the next time I comment.' => __( 'Save my name, email, and website in this browser for the next time I comment.', 'jetpack-mu-wpcom' ),
'siteId' => $this->blog_id,
'postId' => $post_id,
'mustLogIn' => $comment_registration_enabled && ! is_user_logged_in(),
'requireNameEmail' => boolval( get_blog_option( $this->blog_id, 'require_name_email' ) ),
'commentRegistration' => $comment_registration_enabled,
'connectURL' => $connect_url,
'logoutURL' => html_entity_decode( wp_logout_url(), ENT_COMPAT ),
'homeURL' => home_url( '/' ),
'subscribeToBlog' => $subscribe_to_blog,
'subscribeToComment' => $subscribe_to_comment,
'isJetpackCommentsLoggedIn' => is_jetpack_comments() && is_jetpack_comments_user_logged_in(),
'jetpackUsername' => $jetpack_username,
'jetpackUserId' => $jetpack_user_id,
'jetpackSignature' => $jetpack_signature,
'jetpackAvatar' => $jetpack_avatar,
'enableBlocks' => boolval( $this->should_load_gutenberg_comments() ),
'enableSubscriptionModal' => boolval( $this->should_show_subscription_modal() ),
'currentLocale' => $locale,
'isJetpackComments' => is_jetpack_comments(),
'allowedBlocks' => \Verbum_Block_Utils::get_allowed_blocks(),
'embedNonce' => wp_create_nonce( 'embed_nonce' ),
'verbumBundleUrl' => plugins_url( 'dist/index.js', __FILE__ ),
'isRTL' => is_rtl( $locale ),
'vbeCacheBuster' => $vbe_cache_buster,
'siteId' => $this->blog_id,
'postId' => $post_id,
'mustLogIn' => $comment_registration_enabled && ! is_user_logged_in(),
'requireNameEmail' => boolval( get_blog_option( $this->blog_id, 'require_name_email' ) ),
'commentRegistration' => $comment_registration_enabled,
'connectURL' => $connect_url,
'logoutURL' => html_entity_decode( wp_logout_url(), ENT_COMPAT ),
'homeURL' => home_url( '/' ),
'subscribeToBlog' => $subscribe_to_blog,
'subscribeToComment' => $subscribe_to_comment,
'isJetpackCommentsLoggedIn' => is_jetpack_comments() && is_jetpack_comments_user_logged_in(),
'jetpackUsername' => $jetpack_username,
'jetpackUserId' => $jetpack_user_id,
'jetpackSignature' => $jetpack_signature,
'jetpackAvatar' => $jetpack_avatar,
'enableBlocks' => boolval( $this->should_load_gutenberg_comments() ),
'enableSubscriptionModal' => boolval( $this->should_show_subscription_modal() ),
'currentLocale' => $locale,
'isJetpackComments' => is_jetpack_comments(),
'allowedBlocks' => \Verbum_Block_Utils::get_allowed_blocks(),
'embedNonce' => wp_create_nonce( 'embed_nonce' ),
'verbumBundleUrl' => plugins_url( 'dist/index.js', __FILE__ ),
'isRTL' => is_rtl( $locale ),
'vbeCacheBuster' => $vbe_cache_buster,
)
),
'before'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { Hovercards } from '@gravatar-com/hovercards';
import { translate } from '../../i18n';

const convertJsonToUser = ( profile, avatarUrl: string ) => {
// Bit of a nuisance that we have to convert like this
const {
hash,
display_name: displayName,
description,
profile_url: profileUrl,
company,
location,
job_title: jobTitle,
verified_accounts: verifiedAccounts,
} = profile;

return {
hash,
displayName,
description,
avatarUrl,
profileUrl,
company,
location,
jobTitle,
verifiedAccounts: verifiedAccounts.map(
( { url, service_label, service_icon, service_type, is_hidden } ) => ( {
url,
label: service_label,
icon: service_icon,
type: service_type,
isHidden: is_hidden,
} )
),
};
};

const generateSHA256Hash = async ( data: string ) => {
// Encode the data as UTF-8
const encoder = new TextEncoder();
const dataBuffer = encoder.encode( data );

// Compute the hash
const hashBuffer = await window.crypto.subtle.digest( 'SHA-256', dataBuffer );

// Convert the hash to a hexadecimal string
const hashArray = Array.from( new Uint8Array( hashBuffer ) );
return hashArray.map( byte => byte.toString( 16 ).padStart( 2, '0' ) ).join( '' );
};

const getGravatarProfile = async ( emailHash: string, avatarUrl: string ) => {
return fetch( `https://api.gravatar.com/v3/profiles/${ emailHash }?source=hovercard` )
.then( res => {
if ( res.status !== 200 ) {
throw new Error();
}

return res.json();
} )
.then( profile => convertJsonToUser( profile, avatarUrl ) );
};

export const getHovercard = async ( email: string ) => {
const hash = await generateSHA256Hash( email );
const avatarUrl = `https://gravatar.com/avatar/${ hash }`;
let hovercardData = null;

try {
hovercardData = await getGravatarProfile( hash, avatarUrl );
} catch ( error ) {
hovercardData = {
hash: email,
displayName: translate( 'Unknown User' ),
avatarUrl: 'https://gravatar.com/images/gravatars/mysteryman.png',
profileUrl: 'https://gravatar.com/connect/?email=' + encodeURIComponent( email ),
description: translate(
'This site uses Gravatar to manage avatars and profiles, but we can’t find an account for this email address.'
),
isUnknown: true,
};
}

return Hovercards.createHovercard( hovercardData, {
i18n: {
'View profile': hovercardData.isUnknown
? translate( 'Is this you? Claim your free profile' )
: translate( 'View profile' ),
},
} );
};
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import { getUserInfoCookie, isAuthRequired } from '../../utils';
import { NewCommentEmail } from '../new-comment-email';
import { NewPostsEmail } from '../new-posts-email';
import { EmailFormCookieConsent } from './email-form-cookie-consent';
import { getHovercard } from './hovercard';
import type { ChangeEvent } from 'preact/compat';
import '@gravatar-com/hovercards/dist/style.css';
import './style.scss';

interface EmailFormProps {
Expand Down Expand Up @@ -44,6 +46,8 @@ export const EmailForm = ( { shouldShowEmailForm }: EmailFormProps ) => {
const { subscribeToComment, subscribeToBlog } = VerbumComments;
const [ emailNewComment, setEmailNewComment ] = useState( false );
const [ emailNewPosts, setEmailNewPosts ] = useState( false );
const [ hovercard, setHovercard ] = useState( null );
const [ profileEmails, setProfileEmails ] = useState( [] );
const [ deliveryFrequency, setDeliveryFrequency ] = useState( 'instantly' );
const authRequired = isAuthRequired();
const dispose = effect( () => {
Expand Down Expand Up @@ -75,6 +79,17 @@ export const EmailForm = ( { shouldShowEmailForm }: EmailFormProps ) => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [] );

const blurEmail = async () => {
if ( profileEmails.includes( userEmail.value ) ) {
return;
}

const card = await getHovercard( userEmail.value );

setHovercard( card.outerHTML );
setProfileEmails( [ ...profileEmails, userEmail.value ] );
};

return (
<div
className={ clsx( 'verbum-form', {
Expand All @@ -100,6 +115,7 @@ export const EmailForm = ( { shouldShowEmailForm }: EmailFormProps ) => {
isEmailTouched.value = true;
setFormData( event );
} }
onBlur={ isValidEmail.value && blurEmail }
value={ userEmail }
name="email"
placeholder={ `${ translate( 'Email' ) } ${ translate(
Expand All @@ -108,6 +124,25 @@ export const EmailForm = ( { shouldShowEmailForm }: EmailFormProps ) => {
/>
</label>

{ /* eslint-disable-next-line jsx-a11y/label-has-associated-control -- https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/issues/869 */ }
{ hovercard && (
<div className="verbum__gravatar">
<div
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={ { __html: hovercard } }
/>
<p>
<a
href="https://support.gravatar.com/profiles/hovercards/"
target="_blank"
rel="noreferrer"
>
{ translate( 'What is a Gravatar card?' ) }
</a>
</p>
</div>
) }

{ /* eslint-disable-next-line jsx-a11y/label-has-associated-control -- https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/issues/869 */ }
<label className="verbum__label">
<Name />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
width: 100%;
height: 100%;
padding-left: 48px;
/* Override border styling */
/* Override border styling */
border: none !important;
font-size: 14px;
box-sizing: border-box;
Expand Down Expand Up @@ -73,4 +73,22 @@
display: block;
}
}

.verbum__gravatar {
padding: 32px 64px;
background-color: #EBEBEB;

.gravatar-hovercard__inner,
.gravatar-hovercard {
width: 100%;
}

> p {
text-align: center;
margin-top: 16px;
font-size: 11px;
font-family: SF Pro Text,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif;
line-height: 16px;
}
}
}
Loading