mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-04 17:04:59 +03:00
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:
parent
0867279504
commit
b85ac98368
@ -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');
|
||||
|
80
core/client/app/components/gh-profile-image.js
Normal file
80
core/client/app/components/gh-profile-image.js
Normal 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'));
|
||||
}
|
||||
}
|
||||
});
|
@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -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;
|
||||
}
|
||||
|
20
core/client/app/templates/components/gh-profile-image.hbs
Normal file
20
core/client/app/templates/components/gh-profile-image.hbs
Normal 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>
|
@ -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">
|
||||
|
43
core/client/tests/unit/components/gh-profile-image-test.js
Normal file
43
core/client/tests/unit/components/gh-profile-image-test.js
Normal 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);
|
||||
});
|
||||
}
|
||||
);
|
Loading…
Reference in New Issue
Block a user