added profile image component

closes #5334
- adds component for profile images (with optional gravatar)
- integrates image profile component into setup form
This commit is contained in:
Austin Burdine 2015-05-27 23:52:41 -06:00
parent 0867279504
commit b85ac98368
7 changed files with 200 additions and 30 deletions

View File

@ -46,6 +46,9 @@ app.import('bower_components/keymaster/keymaster.js');
app.import('bower_components/devicejs/lib/device.js');
app.import('bower_components/jquery-ui/ui/jquery-ui.js');
app.import('bower_components/jquery-file-upload/js/jquery.fileupload.js');
app.import('bower_components/blueimp-load-image/js/load-image.all.min.js');
app.import('bower_components/jquery-file-upload/js/jquery.fileupload-process.js');
app.import('bower_components/jquery-file-upload/js/jquery.fileupload-image.js');
app.import('bower_components/google-caja/html-css-sanitizer-bundle.js');
app.import('bower_components/jqueryui-touch-punch/jquery.ui.touch-punch.js');
app.import('bower_components/codemirror/lib/codemirror.js');

View File

@ -0,0 +1,80 @@
/* global md5 */
import Ember from 'ember';
/**
* A component to manage a user profile image. By default it just handles picture uploads,
* but if passed a bound 'email' property it will render the user's gravatar image
*
* Example: {{gh-profile-image email=controllerEmailProperty setImage="controllerActionName"}}
*
* @param {int} size The size of the image to render
* @param {String} email Reference to a bound email object if gravatar image behavior is desired.
* @param {String} setImage The string name of the action on the controller to be called when an image is added.
* @property {Boolean} hasUploadedImage Whether or not the user has uploaded an image (whether or not to show the default image/gravatar image)
* @property {String} defaultImage String containing the background-image css property of the default user profile image
* @property {String} imageBackground String containing the background-image css property with the gravatar url
*/
export default Ember.Component.extend({
email: '',
size: 90,
hasUploadedImage: false,
ghostPaths: Ember.inject.service('ghost-paths'),
hasEmail: Ember.computed.notEmpty('email'),
defaultImage: Ember.computed('ghostPaths', function () {
var url = this.get('ghostPaths.url').asset('/shared/img/user-image.png');
return `background-image: url(${url})`.htmlSafe();
}),
imageBackground: Ember.computed('email', 'size', function () {
var email = this.get('email'),
size = this.get('size'),
url;
if (email) {
url = 'http://www.gravatar.com/avatar/' + md5(email) + '?s=' + size + '&d=blank';
return `background-image: url(${url})`.htmlSafe();
}
}),
didInsertElement: function () {
var size = this.get('size'),
uploadElement = this.$('.js-file-input');
// while theoretically the 'add' and 'processalways' functions could be
// added as properties of the hash passed to fileupload(), for some reason
// they needed to be placed in an on() call for the add method to work correctly
uploadElement.fileupload({
url: this.get('ghostPaths.url').api('uploads'),
dropZone: this.$('.js-img-dropzone'),
previewMaxHeight: size,
previewMaxWidth: size,
previewCrop: true,
maxNumberOfFiles: 1,
autoUpload: false,
acceptFileTypes: /(\.|\/)(gif|jpe?g|png|svg?z)$/i
})
.on('fileuploadadd', Ember.run.bind(this, this.queueFile))
.on('fileuploadprocessalways', Ember.run.bind(this, this.triggerPreview));
},
willDestroyElement: function () {
this.$('.js-file-input').fileupload('destroy');
},
queueFile: function (e, data) {
this.set('hasUploadedImage', true);
// send image data to controller
this.sendAction('setImage', data);
},
triggerPreview: function (e, data) {
var file = data.files[data.index];
if (file.preview) {
// necessary jQuery code because file.preview is a raw DOM object
// potential todo: rename 'gravatar-img' class in the CSS to be something
// that both the gravatar and the image preview can use that's not so confusing
this.$('.js-img-preview').empty().append(this.$(file.preview).addClass('gravatar-img'));
}
}
});

View File

@ -1,4 +1,3 @@
/* global md5 */
import Ember from 'ember';
import {request as ajax} from 'ic-ajax';
import ValidationEngine from 'ghost/mixins/validation-engine';
@ -16,26 +15,35 @@ export default Ember.Controller.extend(ValidationEngine, {
notifications: Ember.inject.service(),
application: Ember.inject.controller(),
gravatarUrl: Ember.computed('email', function () {
var email = this.get('email'),
size = this.get('size');
return 'http://www.gravatar.com/avatar/' + md5(email) + '?s=' + size + '&d=blank';
}),
userImage: Ember.computed('gravatarUrl', function () {
return this.get('image') || this.get('gravatarUrl');
}),
userImageBackground: Ember.computed('userImage', function () {
return 'background-image: url(' + this.get('userImage') + ')';
}),
invalidMessage: 'The password fairy does not approve',
// ValidationEngine settings
validationType: 'setup',
/**
* Uploads the given data image, then sends the changed user image property to the server
* @param {Object} user User object, returned from the 'setup' api call
* @return {Ember.RSVP.Promise} A promise that takes care of both calls
*/
sendImage: function (user) {
var self = this,
image = this.get('image');
return new Ember.RSVP.Promise(function (resolve, reject) {
image.formData = {};
image.submit()
.success(function (response) {
user.image = response;
ajax({
url: self.get('ghostPaths.url').api('users', user.id.toString()),
type: 'PUT',
data: {
users: [user]
}
}).then(resolve).catch(reject);
})
.error(reject);
});
},
actions: {
setup: function () {
var self = this,
@ -56,7 +64,7 @@ export default Ember.Controller.extend(ValidationEngine, {
blogTitle: data.blogTitle
}]
}
}).then(function () {
}).then(function (result) {
// Don't call the success handler, otherwise we will be redirected to admin
self.get('application').set('skipAuthSuccessHandler', true);
@ -65,7 +73,17 @@ export default Ember.Controller.extend(ValidationEngine, {
password: self.get('password')
}).then(function () {
self.set('password', '');
self.transitionToRoute('setup.three');
if (data.image) {
self.sendImage(result.users[0])
.then(function () {
self.transitionToRoute('setup.three');
}).catch(function (resp) {
notifications.showAPIError(resp);
});
} else {
self.transitionToRoute('setup.three');
}
});
}).catch(function (resp) {
self.toggleProperty('submitting');
@ -75,6 +93,9 @@ export default Ember.Controller.extend(ValidationEngine, {
self.toggleProperty('submitting');
self.set('showError', true);
});
},
setImage: function (image) {
this.set('image', image);
}
}
});

View File

@ -311,6 +311,18 @@
animation: fade-in 1s;
}
.gh-flow-content .file-uploader {
position: absolute;
right: 0;
margin: 0;
font-size: 23px;
opacity: 0;
cursor: pointer;
transform: scale(14);
transform-origin: right;
direction: ltr;
}
.gh-flow-content .form-group {
margin-bottom: 2.5rem;
}

View File

@ -0,0 +1,20 @@
<figure class="account-image js-file-upload">
{{#unless hasUploadedImage}}
<div class="placeholder-img" style={{defaultImage}}></div>
{{#if hasEmail}}
<div id="account-image" class="gravatar-img" style={{imageBackground}}>
<span class="sr-only">User image</span>
</div>
{{/if}}
{{/unless}}
<div class="js-img-preview"></div>
<span class="edit-account-image js-img-dropzone">
<i class="icon-photos">
<span class="sr-only">Upload an image</span>
</i>
</span>
<input type="file" class="file-uploader js-file-input" name="uploadimage">
</figure>

View File

@ -8,16 +8,7 @@
<input style="display:none;" type="text" name="fakeusernameremembered"/>
<input style="display:none;" type="password" name="fakepasswordremembered"/>
<figure class="account-image">
<div class="placeholder-img" style="background-image: url({{gh-path 'admin' 'img/ghosticon.jpg'}})"></div>
<!-- TODO: fix/change this to prevent XSS -->
<div id="account-image" class="gravatar-img" style="{{userImageBackground}}">
<span class="sr-only">User image</span>
</div>
<a class="edit-account-image" href="#"><i class="icon-photos"><span class="sr-only">Upload an image</span></i></a>
</figure>
{{gh-profile-image email=email setImage="setImage"}}
{{#gh-form-group errors=errors property="email"}}
<label for="email-address">Email address</label>
<span class="input-icon icon-mail">

View File

@ -0,0 +1,43 @@
/* jshint expr:true */
/* global md5 */
import { expect } from 'chai';
import {
describeComponent,
it
} from 'ember-mocha';
describeComponent('gh-profile-image', 'GhProfileImageComponent', {
needs: ['service:ghost-paths']
}, function () {
it('renders', function () {
// creates the component instance
var component = this.subject();
expect(component._state).to.equal('preRender');
// renders the component on the page
this.render();
expect(component._state).to.equal('inDOM');
});
it('renders the gravatar image background if email is supplied', function () {
var component = this.subject(),
testEmail = 'test@example.com',
style, size;
component.set('email', testEmail);
this.render();
size = component.get('size');
style = 'url(http://www.gravatar.com/avatar/' + md5(testEmail) + '?s=' + size + '&d=blank)';
expect(component.$('#account-image').css('background-image')).to.equal(style);
});
it('doesn\'t render the gravatar image background if email isn\'t supplied', function () {
var component = this.subject();
this.render();
expect(component.$('#account-image').length).to.equal(0);
});
}
);