diff --git a/client/blocks/comments/form.jsx b/client/blocks/comments/form.jsx index 69293a4c183a..4f18a581e763 100644 --- a/client/blocks/comments/form.jsx +++ b/client/blocks/comments/form.jsx @@ -1,11 +1,12 @@ +import './form.scss'; import { Button, FormInputValidation } from '@automattic/components'; import clsx from 'clsx'; import { localize, useTranslate } from 'i18n-calypso'; import PropTypes from 'prop-types'; import { Component } from 'react'; import { connect } from 'react-redux'; +import UserAvatar from 'calypso/blocks/user-avatar'; import FormFieldset from 'calypso/components/forms/form-fieldset'; -import GravatarWithHovercards from 'calypso/components/gravatar-with-hovercards'; import { ProtectFormGuard } from 'calypso/lib/protect-form'; import { recordAction, recordGaEvent, recordTrackForPost, getLocation } from 'calypso/reader/stats'; import { writeComment, deleteComment, replyComment } from 'calypso/state/comments/actions'; @@ -15,8 +16,6 @@ import { getCurrentRoute } from 'calypso/state/selectors/get-current-route'; import { getPreviousPath } from 'calypso/state/selectors/get-previous-path'; import AutoresizingFormTextarea from './autoresizing-form-textarea'; -import './form.scss'; - const noop = () => {}; function PostCommentFormError( { type } ) { @@ -149,7 +148,7 @@ class PostCommentForm extends Component { } render() { - const { post, error, errorType, translate } = this.props; + const { post, error, errorType, translate, currentUser } = this.props; // Don't display the form if comments are closed if ( post && post.discussion && post.discussion.comments_open === false ) { @@ -174,7 +173,13 @@ class PostCommentForm extends Component {
- + {}; /** @@ -319,12 +319,10 @@ class PostComment extends PureComponent { } else if ( commentAuthor.site_ID ) { commentAuthorUrl = getStreamUrl( null, commentAuthor.site_ID ); } else { - const urlToCheck = commentAuthor?.URL; + const urlToCheck = prependHTTPS( commentAuthor?.URL ); if ( urlToCheck && isURL( urlToCheck ) ) { - const protocol = getProtocol( urlToCheck ); const domain = getAuthority( urlToCheck ); - // isURL uses URL() which allows '%20' in Chromium but not Firefox, so we check ourselves. - if ( protocol === 'https:' && ! domain.includes( '%' ) ) { + if ( ! domain.includes( '%' ) ) { commentAuthorUrl = urlToCheck; } } @@ -334,7 +332,9 @@ class PostComment extends PureComponent { }; renderAuthorTag = ( { authorName, authorUrl, commentId, className } ) => { - return authorUrl ? ( + const isExternalUrl = authorUrl?.startsWith( '/reader/users/' ) === false; + + return authorUrl && ! isExternalUrl ? ( { authorName } + + { isExternalUrl && ( + + + + ) } ); }; @@ -446,13 +457,7 @@ class PostComment extends PureComponent { return (
  • - { commentAuthorUrl ? ( - - - - ) : ( - - ) } + { this.renderAuthorTag( { authorUrl: commentAuthorUrl, diff --git a/client/blocks/comments/post-comment.scss b/client/blocks/comments/post-comment.scss index 529a5d370297..15f91fa27c2d 100644 --- a/client/blocks/comments/post-comment.scss +++ b/client/blocks/comments/post-comment.scss @@ -8,7 +8,7 @@ &.depth-2 { padding-left: 42px; - > .comments__comment-author .gravatar { + > .comments__comment-author .user-avatar { left: 0; } } @@ -68,10 +68,9 @@ font-size: $font-body-small; font-weight: 600; - .gravatar { - border-radius: 48px; /* stylelint-disable-line scales/radii */ + .user-avatar { position: absolute; - top: 8px; + top: 4px; left: -41px; } } @@ -95,6 +94,16 @@ a.comments__comment-username { } } +.comments__external-link { + margin-left: 2px; + position: relative; + top: 3px; + + svg { + fill: var(--color-link); + } +} + .comments__comment-trackbackicon { background-color: var(--color-neutral-0); border-radius: 50%; diff --git a/client/blocks/comments/post-trackback.tsx b/client/blocks/comments/post-trackback.tsx index e9fdae539030..2427c21e1672 100644 --- a/client/blocks/comments/post-trackback.tsx +++ b/client/blocks/comments/post-trackback.tsx @@ -33,7 +33,6 @@ export default function PostTrackback( props: PostTrackbackProps ): JSX.Element return null; } const unescapedAuthorName = unescape( get( comment, 'author.name', '' ) ); - const authorUrlLink = comment.author?.wpcom_login ? getUserProfileUrl( comment.author.wpcom_login ) : comment.author?.URL; diff --git a/client/blocks/reader-full-post/header-meta.jsx b/client/blocks/reader-full-post/header-meta.jsx index bfd5b257b6dd..a151525657a1 100644 --- a/client/blocks/reader-full-post/header-meta.jsx +++ b/client/blocks/reader-full-post/header-meta.jsx @@ -17,11 +17,7 @@ const ReaderFullPostHeaderMeta = ( { post, author, siteName, feedId, siteId } ) return (
    - +
    { showAuthorLink && ( diff --git a/client/blocks/reader-full-post/style.scss b/client/blocks/reader-full-post/style.scss index 3e55c8647a88..c7115885bbb3 100644 --- a/client/blocks/reader-full-post/style.scss +++ b/client/blocks/reader-full-post/style.scss @@ -99,20 +99,6 @@ $reader-full-post-desktop-min: $reader-full-post-sidebar-width + $reader-full-po } } -.reader-full-post__header-meta-avatars { - flex-shrink: 0; - - .gravatar { - border-radius: 50%; - width: 40px; - height: 40px; - } - - .gridicons-globe { - fill: var( --color-neutral-70 ); - } -} - .reader-full-post__header-meta-info { flex: 1; min-width: 0; diff --git a/client/blocks/reader-subscription-list-item/placeholder.jsx b/client/blocks/reader-subscription-list-item/placeholder.tsx similarity index 58% rename from client/blocks/reader-subscription-list-item/placeholder.jsx rename to client/blocks/reader-subscription-list-item/placeholder.tsx index 10139178d5a3..3a5464cd1eb5 100644 --- a/client/blocks/reader-subscription-list-item/placeholder.jsx +++ b/client/blocks/reader-subscription-list-item/placeholder.tsx @@ -1,25 +1,29 @@ -import UserAvatar from 'calypso/blocks/user-avatar'; +import { useTranslate } from 'i18n-calypso'; + +const ReaderSubscriptionListItemPlaceholder = (): JSX.Element => { + const translate = useTranslate(); -const ReaderSubscriptionListItemPlaceholder = () => { return (
    - +
    - Site title + + { translate( 'Site title' ) } +
    - Description of the site + { translate( 'The description of the site.' ) }
    - by author name + { translate( 'Author name' ) } www.example.com
    -
    Follow here
    +
    { translate( 'Follow here' ) }
    ); diff --git a/client/blocks/reader-subscription-list-item/style.scss b/client/blocks/reader-subscription-list-item/style.scss index f1712f426b94..ca00d5fe5d5b 100644 --- a/client/blocks/reader-subscription-list-item/style.scss +++ b/client/blocks/reader-subscription-list-item/style.scss @@ -24,13 +24,6 @@ -webkit-box-orient: vertical; } -.reader-subscription-list-item .gravatar { - float: left; - height: 32px; - min-height: 32px; - min-width: 32px; -} - .reader-subscription-list-item .site-icon { margin-top: 4px; } @@ -182,6 +175,7 @@ // Placeholders .reader-subscription-list-item.reader-subscription-list-item__placeholder { + .reader-subscription-list-item__site-avatar, .reader-subscription-list-item__site-title, .reader-subscription-list-item__site-excerpt, .reader-subscription-list-item__by-text, @@ -195,6 +189,13 @@ max-width: 80%; } + .reader-subscription-list-item__site-avatar { + border-radius: 4px; + display: inline-block; + width: 32px; + height: 32px; + } + .reader-subscription-list-item__site-title { max-width: 60%; } diff --git a/client/blocks/reader-suggested-follows/dialog.jsx b/client/blocks/reader-suggested-follows/dialog.jsx index c8bb6ca3a5aa..c11c52eeb4bc 100644 --- a/client/blocks/reader-suggested-follows/dialog.jsx +++ b/client/blocks/reader-suggested-follows/dialog.jsx @@ -165,7 +165,7 @@ const ReaderSuggestedFollowsDialog = ( {
  • ) } diff --git a/client/blocks/reader-suggested-follows/style.scss b/client/blocks/reader-suggested-follows/style.scss index 999d6e6c3daa..293f5a70337b 100644 --- a/client/blocks/reader-suggested-follows/style.scss +++ b/client/blocks/reader-suggested-follows/style.scss @@ -20,7 +20,6 @@ padding: 0 32px 10px 32px; } -; .reader-recommended-follows-dialog__body { scrollbar-width: thin; scrollbar-gutter: stable; @@ -171,8 +170,9 @@ .is-placeholder.placeholder-title { width: 50%; - height: 48 + height: 48px; } + .is-placeholder.placeholder-description { height: 24px; } diff --git a/client/blocks/user-avatar/docs/example.tsx b/client/blocks/user-avatar/docs/example.tsx index 477334c88557..4cd791b6f9e9 100644 --- a/client/blocks/user-avatar/docs/example.tsx +++ b/client/blocks/user-avatar/docs/example.tsx @@ -10,8 +10,6 @@ const UserAvatarExample = (): JSX.Element => { return (
    -

    Compact

    -
    ); }; diff --git a/client/blocks/user-avatar/index.tsx b/client/blocks/user-avatar/index.tsx index ba6d9af0c688..f0e1b9e8bb77 100644 --- a/client/blocks/user-avatar/index.tsx +++ b/client/blocks/user-avatar/index.tsx @@ -1,53 +1,74 @@ -import './style.scss'; -import clsx from 'clsx'; -import GravatarWithHovercards from 'calypso/components/gravatar-with-hovercards'; +import { Popover } from '@wordpress/components'; +import { useRef, useState } from 'react'; +import UserHovercard from 'calypso/blocks/user-avatar/user-hovercard'; +import PreloadedImage from 'calypso/components/preloaded-image'; +import UserAvatarDefaultIcon from 'calypso/reader/components/icons/user-avatar-default-icon'; +import { useGetReaderUserQuery } from 'calypso/reader/user-profile/queries/useGetReaderUserQuery'; import { getUserProfileUrl } from 'calypso/reader/user-profile/user-profile.utils'; - -const noop = () => undefined; +import { getProcessedGravatarUrl } from './utils'; type UserAvatarProps = { - className?: string; user?: UserAvatarInfo | null; - isCompact?: boolean; // Show a small version of the avatar. Used in post cards and streams. - onClick?: () => void; // Click handler to be executed when avatar is clicked. - iconSize?: number | null; + size?: number; + hideHovercard?: boolean; }; -export type UserAvatarInfo = { - ID?: number; +export interface UserAvatarInfo { + ID?: number; // Represents user ID on source website i.e. WPCOM, Jetpack site, etc. avatar_URL?: string; display_name?: string; name?: string; - login?: string; + description?: string; + login?: string; // Represents username on source website i.e. WPCOM, Jetpack site, etc. + profile_URL?: string; + wpcom_id?: number; wpcom_login?: string; -}; - -export default function UserAvatar( { - className, - user, - isCompact = false, - onClick = noop, - iconSize = null, -}: UserAvatarProps ) { - // GravatarWithHovercards component display default avatar if user an empty object. Nothing when user is null or undefined. - if ( ! user ) { - user = {}; - } +} - if ( ! iconSize ) { - iconSize = isCompact ? 40 : 96; - } +export default function UserAvatar( { user, size = 32, hideHovercard = false }: UserAvatarProps ) { + const [ isHovered, setIsHovered ] = useState( false ); + const avatarRef = useRef< HTMLDivElement >( null ); + const wpcomProfileUrl = user?.wpcom_login ? getUserProfileUrl( user?.wpcom_login ) : null; // Only navigate to profile page. Avoid navigating to any external links to keep UX consistent. + const name = user?.display_name || user?.name || ''; + const avatarUrl = user?.avatar_URL ? getProcessedGravatarUrl( user.avatar_URL ) : null; + const avatarImg = avatarUrl ? ( + + ) : ( + + ); - const classes = clsx( 'user-avatar', 'has-gravatar', className, { - 'is-compact': isCompact, - } ); - const avatarUrl = user?.wpcom_login ? getUserProfileUrl( user.wpcom_login ) : null; - const userGravatar = ; - const avatarElement = avatarUrl ? { userGravatar } : userGravatar; + // Prefetching so that we can display WPCOM users Hovercards instantly, Gravatar lookups will be triggered on hover. + useGetReaderUserQuery( user?.wpcom_login, user?.wpcom_id ); return ( -