-
Notifications
You must be signed in to change notification settings - Fork 2k
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
Gravatar: Show current user's local image #9369
Changes from all commits
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 |
---|---|---|
@@ -1,73 +1,106 @@ | ||
/** | ||
* 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'; | ||
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( { | ||
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: 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, | ||
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. Is this in 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. Hmm! Not really sure about adding a comment here since it's not obvious at first glance what 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 it's in README (sorry for not noticing), it's good enough. :) |
||
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 ( | ||
<span | ||
className="gravatar is-placeholder" | ||
style={ { width: size, height: size } } | ||
/> | ||
); | ||
} | ||
|
||
if ( ! this.props.user ) { | ||
return <span className="gravatar is-placeholder" style={ { width: size, height: size } } />; | ||
} else if ( this.state.failedToLoad ) { | ||
if ( this.state.failedToLoad ) { | ||
return <span className="gravatar is-missing" />; | ||
} | ||
|
||
const alt = this.props.alt || this.props.user.display_name; | ||
const avatarURL = this.getResizedImageURL( safeImageURL( this.props.user.avatar_URL ) ); | ||
const altText = alt || user.display_name; | ||
|
||
const avatarURL = ( | ||
tempImage || | ||
this.getResizedImageURL( safeImageURL( user.avatar_URL ) ) | ||
); | ||
|
||
return ( | ||
<img alt={ alt } className="gravatar" src={ avatarURL } width={ size } height={ size } onError={ this.onError } /> | ||
<img | ||
alt={ altText } | ||
className="gravatar" | ||
src={ avatarURL } | ||
width={ size } | ||
height={ size } | ||
onError={ this.onError } | ||
/> | ||
); | ||
} | ||
} ); | ||
} | ||
|
||
export default connect( ( state, ownProps ) => ( { | ||
tempImage: getUserTempGravatar( state, get( ownProps, 'user.ID', false ) ), | ||
} ) )( Gravatar ); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 = <Gravatar user={ bobTester } />, | ||
expectedResultString = '<img alt="Bob The Tester" class="gravatar" src="https://0.gravatar.com/avatar/cf55adb1a5146c0a11a808bce7842f7b?s=96&d=mm" width="32" height="32"/>'; | ||
const gravatar = <Gravatar user={ bobTester } />; | ||
const expectedResultString = '<img alt="Bob The Tester" ' + | ||
'class="gravatar" ' + | ||
'src="https://0.gravatar.com/' + | ||
'avatar/cf55adb1a5146c0a11a808bce7842f7b?s=96&d=mm" ' + | ||
'width="32" height="32"/>'; | ||
|
||
assert.equal( expectedResultString, stripReactAttributes( ReactDomServer.renderToStaticMarkup( gravatar ) ) ); | ||
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. @v18 I highly recommend you check out 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. @dmsnell - these tests were written before my time. I'm not sure that updating the tests for this PR falls under the scope here :) The tests I wrote for a different PR use enzyme <3 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. @v18 as you will. I would hate to add more cruft if it's not too much more work to update there. wouldn't have to change the whole file. 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. @dmsnell I know what you mean :) On the other hand, to change the tests, I'd need to rework the entire file, no? They all rely on checking the final HTML output vs checking properties. |
||
} ); | ||
|
||
it( 'should update the width and height when given a size attribute', function() { | ||
var gravatar = <Gravatar user={ bobTester } size={ 100 } />, | ||
expectedResultString = '<img alt="Bob The Tester" class="gravatar" src="https://0.gravatar.com/avatar/cf55adb1a5146c0a11a808bce7842f7b?s=96&d=mm" width="100" height="100"/>'; | ||
const gravatar = <Gravatar user={ bobTester } size={ 100 } />; | ||
const expectedResultString = '<img alt="Bob The Tester" ' + | ||
'class="gravatar" ' + | ||
'src="https://0.gravatar.com/' + | ||
'avatar/cf55adb1a5146c0a11a808bce7842f7b?s=96&d=mm" ' + | ||
'width="100" height="100"/>'; | ||
|
||
assert.equal( expectedResultString, stripReactAttributes( ReactDomServer.renderToStaticMarkup( gravatar ) ) ); | ||
} ); | ||
|
||
it( 'should update source image when given imgSize attribute', function() { | ||
var gravatar = <Gravatar user={ bobTester } imgSize={ 200 } />, | ||
expectedResultString = '<img alt="Bob The Tester" class="gravatar" src="https://0.gravatar.com/avatar/cf55adb1a5146c0a11a808bce7842f7b?s=200&d=mm" width="32" height="32"/>'; | ||
const gravatar = <Gravatar user={ bobTester } imgSize={ 200 } />; | ||
const expectedResultString = '<img alt="Bob The Tester" ' + | ||
'class="gravatar" ' + | ||
'src="https://0.gravatar.com/' + | ||
'avatar/cf55adb1a5146c0a11a808bce7842f7b?s=200&d=mm" ' + | ||
'width="32" height="32"/>'; | ||
|
||
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 = <Gravatar user={ noImageTester } />, | ||
expectedResultString = '<img alt="Bob The Tester" class="gravatar" src="https://www.gravatar.com/avatar/0?s=96&d=mm" width="32" height="32"/>'; | ||
const noImageTester = { display_name: 'Bob The Tester' }; | ||
const gravatar = <Gravatar user={ noImageTester } />; | ||
const expectedResultString = '<img alt="Bob The Tester" ' + | ||
'class="gravatar" ' + | ||
'src="https://www.gravatar.com/avatar/0?s=96&d=mm" ' + | ||
'width="32" height="32"/>'; | ||
|
||
assert.equal( expectedResultString, stripReactAttributes( ReactDomServer.renderToStaticMarkup( gravatar ) ) ); | ||
} ); | ||
|
||
it( 'should allow overriding the alt attribute', function() { | ||
var gravatar = <Gravatar user={ bobTester } alt="Alternate Alt" />, | ||
expectedResultString = '<img alt="Alternate Alt" class="gravatar" src="https://0.gravatar.com/avatar/cf55adb1a5146c0a11a808bce7842f7b?s=96&d=mm" width="32" height="32"/>'; | ||
const gravatar = <Gravatar user={ bobTester } alt="Alternate Alt" />; | ||
const expectedResultString = '<img alt="Alternate Alt" ' + | ||
'class="gravatar" ' + | ||
'src="https://0.gravatar.com/' + | ||
'avatar/cf55adb1a5146c0a11a808bce7842f7b?s=96&d=mm" ' + | ||
'width="32" height="32"/>'; | ||
|
||
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 = <Gravatar user={ nonSecureTester } />, | ||
expectedResultString = '<img class="gravatar" src="https://i2.wp.com/www.example.com/avatar?resize=96%2C96" width="32" height="32"/>'; | ||
const nonSecureTester = { avatar_URL: 'http://www.example.com/avatar' }; | ||
const gravatar = <Gravatar user={ nonSecureTester } />; | ||
const expectedResultString = '<img class="gravatar" ' + | ||
'src="https://i2.wp.com/www.example.com/avatar?resize=96%2C96" ' + | ||
'width="32" height="32"/>'; | ||
|
||
assert.equal( expectedResultString, stripReactAttributes( ReactDomServer.renderToStaticMarkup( gravatar ) ) ); | ||
} ); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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' ); | ||
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. Maybe add a blank line below so old 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 tried it out with a new line, and the extra space makes it seem like 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. We should just follow up by porting the rest of the requires to imports in a janitorial PR. Spacing isn't a huge deal for now. |
||
import Gravatar from 'components/gravatar'; | ||
|
||
module.exports = React.createClass( { | ||
displayName: 'DeleteUser', | ||
|
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.
Instead of having to write constructor just to set the initial state, I discovered that we can use ES7 static properties to define the initial state as well. This is how:
Note: I'm not 100% sure it'll work, actually. Haven't tried myself. :)
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.
You mean property initializers right, since we don't want this to be static. 😄 I'm fine with the above usage
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.
Leaving as-is for now.