From 84364ed765c2c67401eaa4d3764c24dd9d4d9346 Mon Sep 17 00:00:00 2001 From: Valerie Date: Thu, 17 Nov 2016 16:54:23 +0100 Subject: [PATCH 1/2] Show current user's local Gravatar image --- client/blocks/edit-gravatar/test/index.jsx | 2 +- client/components/gravatar/README.md | 6 ++++- client/components/gravatar/index.jsx | 24 ++++++++++++++++--- client/components/user/index.jsx | 4 ++-- client/my-sites/people/delete-user/index.jsx | 2 +- client/post-editor/editor-author/index.jsx | 22 ++++++++++------- .../gravatar-status/test/selectors.js | 3 ++- 7 files changed, 46 insertions(+), 17 deletions(-) diff --git a/client/blocks/edit-gravatar/test/index.jsx b/client/blocks/edit-gravatar/test/index.jsx index 13ba62180d2c9..3f6cd487208cb 100644 --- a/client/blocks/edit-gravatar/test/index.jsx +++ b/client/blocks/edit-gravatar/test/index.jsx @@ -43,7 +43,7 @@ describe( 'EditGravatar', function() { before( function() { EditGravatar = require( 'blocks/edit-gravatar' ).EditGravatar; FilePicker = require( 'components/file-picker' ); - Gravatar = require( 'components/gravatar' ); + Gravatar = require( 'components/gravatar' ).default; ImageEditor = require( 'blocks/image-editor' ); } ); diff --git a/client/components/gravatar/README.md b/client/components/gravatar/README.md index 071f59a74a662..dcd5b13b7365a 100644 --- a/client/components/gravatar/README.md +++ b/client/components/gravatar/README.md @@ -1,7 +1,11 @@ Gravatar ====== -This component is used to display the [Gravatar](https://gravatar.com/) for a user. It takes a User object as a prop and read the images from user.avatar_URL. The images size is set at 96px, used at smaller sizes for retina display. Using one size allows us to only request one image and cache it on the browser. Even if you are displaying it at smaller sizes you should not change the source image. +This component is used to display the [Gravatar](https://gravatar.com/) for a user. It takes a User object as a prop and read the images from user.avatar_URL. + +If the current user has uploaded a new Gravatar recently on Calypso, and therefore has a temporary image set, this component will display the temporary image instead. It reads the temporary image using the redux selector `getUserTempGravatar`. + +The images size is set at 96px, used at smaller sizes for retina display. Using one size allows us to only request one image and cache it on the browser. Even if you are displaying it at smaller sizes you should not change the source image. #### How to use: diff --git a/client/components/gravatar/index.jsx b/client/components/gravatar/index.jsx index e9ce649a2f159..38730e2090fee 100644 --- a/client/components/gravatar/index.jsx +++ b/client/components/gravatar/index.jsx @@ -4,19 +4,29 @@ import React from 'react'; import url from 'url'; import qs from 'querystring'; +import { connect } from 'react-redux'; +import { get } from 'lodash'; /** * Internal dependencies */ import safeImageURL from 'lib/safe-image-url'; +import { + getUserTempGravatar +} from 'state/current-user/gravatar-status/selectors'; -module.exports = React.createClass( { +export const Gravatar = React.createClass( { displayName: 'Gravatar', propTypes: { user: React.PropTypes.object, size: React.PropTypes.number, - imgSize: React.PropTypes.number + imgSize: React.PropTypes.number, + // connected props: + tempImage: React.PropTypes.oneOfType( [ + React.PropTypes.string, // the temp image base64 string if it exists + React.PropTypes.bool // or false if the temp image does not exist + ] ), }, getDefaultProps() { @@ -64,10 +74,18 @@ module.exports = React.createClass( { } const alt = this.props.alt || this.props.user.display_name; - const avatarURL = this.getResizedImageURL( safeImageURL( this.props.user.avatar_URL ) ); + + const avatarURL = ( + this.props.tempImage || + this.getResizedImageURL( safeImageURL( this.props.user.avatar_URL ) ) + ); return ( { ); } } ); + +export default connect( ( state, ownProps ) => ( { + tempImage: getUserTempGravatar( state, get( ownProps, 'user.ID', false ) ), +} ) )( Gravatar ); diff --git a/client/components/user/index.jsx b/client/components/user/index.jsx index 9e3b5524f0a12..6b0e335768ad6 100644 --- a/client/components/user/index.jsx +++ b/client/components/user/index.jsx @@ -1,12 +1,12 @@ /** * External dependencies */ -var React = require( 'react' ); +import React from 'react'; /** * Internal dependencies */ -var Gravatar = require( 'components/gravatar' ); +import Gravatar from 'components/gravatar'; module.exports = React.createClass( { displayName: 'UserItem', diff --git a/client/my-sites/people/delete-user/index.jsx b/client/my-sites/people/delete-user/index.jsx index 763f911af40bb..0d6f20bfa2055 100644 --- a/client/my-sites/people/delete-user/index.jsx +++ b/client/my-sites/people/delete-user/index.jsx @@ -18,9 +18,9 @@ const Card = require( 'components/card' ), FormButtonsBar = require( 'components/forms/form-buttons-bar' ), AuthorSelector = require( 'blocks/author-selector' ), UsersActions = require( 'lib/users/actions' ), - Gravatar = require( 'components/gravatar' ), accept = require( 'lib/accept' ), analytics = require( 'lib/analytics' ); +import Gravatar from 'components/gravatar'; module.exports = React.createClass( { displayName: 'DeleteUser', diff --git a/client/post-editor/editor-author/index.jsx b/client/post-editor/editor-author/index.jsx index 719a9068c558e..29002c0a22159 100644 --- a/client/post-editor/editor-author/index.jsx +++ b/client/post-editor/editor-author/index.jsx @@ -1,18 +1,24 @@ /** * External dependencies */ -const React = require( 'react' ); +import React from 'react'; /** * Internal dependencies */ -const Gravatar = require( 'components/gravatar' ), - user = require( 'lib/user' )(), - AuthorSelector = require( 'blocks/author-selector' ), - PostActions = require( 'lib/posts/actions' ), - touchDetect = require( 'lib/touch-detect' ), - sites = require( 'lib/sites-list' )(), - stats = require( 'lib/posts/stats' ); +import Gravatar from 'components/gravatar'; +import userFactory from 'lib/user'; +import AuthorSelector from 'blocks/author-selector'; +import PostActions from 'lib/posts/actions'; +import touchDetect from 'lib/touch-detect'; +import sitesFactory from 'lib/sites-list'; +import * as stats from 'lib/posts/stats'; + +/** + * Module dependencies + */ +const user = userFactory(); +const sites = sitesFactory(); export default React.createClass( { displayName: 'EditorAuthor', diff --git a/client/state/current-user/gravatar-status/test/selectors.js b/client/state/current-user/gravatar-status/test/selectors.js index 2e2e4f5d584d8..fb95de5843317 100644 --- a/client/state/current-user/gravatar-status/test/selectors.js +++ b/client/state/current-user/gravatar-status/test/selectors.js @@ -45,7 +45,7 @@ describe( 'selectors', () => { const currentUserId = 1; const anotherUserId = 2; - it( 'returns false if user ID is not passed in', () => { + it( 'returns false if user ID is not passed in, or is false', () => { const state = { currentUser: { gravatarStatus: { @@ -57,6 +57,7 @@ describe( 'selectors', () => { } }; expect( getUserTempGravatar( state ) ).to.equal( false ); + expect( getUserTempGravatar( state, false ) ).to.equal( false ); } ); it( 'returns false if the user ID passed is not the current user ID', () => { From 383fa5e0be502bff08ba8c22d2b2c2d798d23fca Mon Sep 17 00:00:00 2001 From: Valerie Date: Thu, 17 Nov 2016 17:10:31 +0100 Subject: [PATCH 2/2] Update to ES6, and clean up syntax --- client/components/gravatar/index.jsx | 91 +++++++++++++---------- client/components/gravatar/test/index.jsx | 60 ++++++++++----- 2 files changed, 93 insertions(+), 58 deletions(-) diff --git a/client/components/gravatar/index.jsx b/client/components/gravatar/index.jsx index 38730e2090fee..dc10a4ad40e86 100644 --- a/client/components/gravatar/index.jsx +++ b/client/components/gravatar/index.jsx @@ -1,7 +1,7 @@ /** * External dependencies */ -import React from 'react'; +import React, { Component, PropTypes } from 'react'; import url from 'url'; import qs from 'querystring'; import { connect } from 'react-redux'; @@ -15,76 +15,91 @@ import { getUserTempGravatar } from 'state/current-user/gravatar-status/selectors'; -export const Gravatar = React.createClass( { - displayName: 'Gravatar', +export class Gravatar extends Component { + constructor() { + super( ...arguments ); + this.state = { + failedToLoad: false + }; + } - propTypes: { - user: React.PropTypes.object, - size: React.PropTypes.number, - imgSize: React.PropTypes.number, + static propTypes = { + user: PropTypes.object, + size: PropTypes.number, + imgSize: PropTypes.number, // connected props: - tempImage: React.PropTypes.oneOfType( [ - React.PropTypes.string, // the temp image base64 string if it exists - React.PropTypes.bool // or false if the temp image does not exist + tempImage: PropTypes.oneOfType( [ + PropTypes.string, // the temp image base64 string if it exists + PropTypes.bool // or false if the temp image does not exist ] ), - }, + }; - getDefaultProps() { + static defaultProps = { // The REST-API returns s=96 by default, so that is most likely to be cached - return { - imgSize: 96, - size: 32 - }; - }, - - getInitialState() { - return { - failedToLoad: false - }; - }, + imgSize: 96, + size: 32 + }; getResizedImageURL( imageURL ) { + const { imgSize } = this.props; imageURL = imageURL || 'https://www.gravatar.com/avatar/0'; const parsedURL = url.parse( imageURL ); const query = qs.parse( parsedURL.query ); if ( /^([-a-zA-Z0-9_]+\.)*(gravatar.com)$/.test( parsedURL.hostname ) ) { - query.s = this.props.imgSize; + query.s = imgSize; query.d = 'mm'; } else { // assume photon - query.resize = this.props.imgSize + ',' + this.props.imgSize; + query.resize = imgSize + ',' + imgSize; } parsedURL.search = qs.stringify( query ); return url.format( parsedURL ); - }, + } - onError() { - this.setState( { failedToLoad: true } ); - }, + onError = () => this.setState( { failedToLoad: true } ); render() { - const size = this.props.size; + const { + alt, + size, + tempImage, + user, + } = this.props; + + if ( ! user ) { + return ( + + ); + } - if ( ! this.props.user ) { - return ; - } else if ( this.state.failedToLoad ) { + if ( this.state.failedToLoad ) { return ; } - const alt = this.props.alt || this.props.user.display_name; + const altText = alt || user.display_name; const avatarURL = ( - this.props.tempImage || - this.getResizedImageURL( safeImageURL( this.props.user.avatar_URL ) ) + tempImage || + this.getResizedImageURL( safeImageURL( user.avatar_URL ) ) ); return ( - { + { ); } -} ); +} export default connect( ( state, ownProps ) => ( { tempImage: getUserTempGravatar( state, get( ownProps, 'user.ID', false ) ), diff --git a/client/components/gravatar/test/index.jsx b/client/components/gravatar/test/index.jsx index b2859acde5c32..6f0c19408e76d 100644 --- a/client/components/gravatar/test/index.jsx +++ b/client/components/gravatar/test/index.jsx @@ -1,15 +1,14 @@ - /** * External dependencies */ -var assert = require( 'assert' ), - ReactDomServer = require( 'react-dom/server' ), - React = require( 'react' ); +import assert from 'assert'; +import ReactDomServer from 'react-dom/server'; +import React from 'react'; /** * Internal dependencies */ -var Gravatar = require( '../' ); +import { Gravatar } from '../'; /** * Pass in a react-generated html string to remove react-specific attributes @@ -22,53 +21,74 @@ function stripReactAttributes( string ) { } describe( 'Gravatar', function() { - var bobTester = { + const bobTester = { avatar_URL: 'https://0.gravatar.com/avatar/cf55adb1a5146c0a11a808bce7842f7b?s=96&d=identicon', display_name: 'Bob The Tester' }; describe( 'rendering', function() { it( 'should render an image given a user with valid avatar_URL, with default width and height 32', function() { - var gravatar = , - expectedResultString = 'Bob The Tester'; + const gravatar = ; + const expectedResultString = 'Bob The Tester'; assert.equal( expectedResultString, stripReactAttributes( ReactDomServer.renderToStaticMarkup( gravatar ) ) ); } ); it( 'should update the width and height when given a size attribute', function() { - var gravatar = , - expectedResultString = 'Bob The Tester'; + const gravatar = ; + const expectedResultString = 'Bob The Tester'; assert.equal( expectedResultString, stripReactAttributes( ReactDomServer.renderToStaticMarkup( gravatar ) ) ); } ); it( 'should update source image when given imgSize attribute', function() { - var gravatar = , - expectedResultString = 'Bob The Tester'; + const gravatar = ; + const expectedResultString = 'Bob The Tester'; assert.equal( expectedResultString, stripReactAttributes( ReactDomServer.renderToStaticMarkup( gravatar ) ) ); } ); it( 'should serve a default image if no avatar_URL available', function() { - var noImageTester = { display_name: 'Bob The Tester' }, - gravatar = , - expectedResultString = 'Bob The Tester'; + const noImageTester = { display_name: 'Bob The Tester' }; + const gravatar = ; + const expectedResultString = 'Bob The Tester'; assert.equal( expectedResultString, stripReactAttributes( ReactDomServer.renderToStaticMarkup( gravatar ) ) ); } ); it( 'should allow overriding the alt attribute', function() { - var gravatar = , - expectedResultString = 'Alternate Alt'; + const gravatar = ; + const expectedResultString = 'Alternate Alt'; assert.equal( expectedResultString, stripReactAttributes( ReactDomServer.renderToStaticMarkup( gravatar ) ) ); } ); // I believe jetpack sites could have custom avatars, so can't assume it's always a gravatar it( 'should promote non-secure avatar urls to secure', function() { - var nonSecureTester = { avatar_URL: 'http://www.example.com/avatar' }, - gravatar = , - expectedResultString = ''; + const nonSecureTester = { avatar_URL: 'http://www.example.com/avatar' }; + const gravatar = ; + const expectedResultString = ''; assert.equal( expectedResultString, stripReactAttributes( ReactDomServer.renderToStaticMarkup( gravatar ) ) ); } );