From b10c68f9a8ec30c52d79e9f1a0c5a433fc4e2855 Mon Sep 17 00:00:00 2001 From: Nazar Gargol Date: Sun, 6 Sep 2015 17:03:49 +0200 Subject: [PATCH 1/3] Adds debounced email validation and gravatar loading on second setup/signup screen closes #5797 - adds debounced email validation as user types - adds debounced gravatar loading for valid email --- core/client/app/components/gh-profile-image.js | 16 +++++++++++++--- core/client/app/controllers/setup/two.js | 11 ----------- core/client/app/controllers/signup.js | 8 -------- core/client/app/templates/setup/two.hbs | 2 +- core/client/app/templates/signup.hbs | 4 ++-- .../unit/components/gh-profile-image-test.js | 5 ++++- 6 files changed, 20 insertions(+), 26 deletions(-) diff --git a/core/client/app/components/gh-profile-image.js b/core/client/app/components/gh-profile-image.js index d468e83606..14f2383b73 100644 --- a/core/client/app/components/gh-profile-image.js +++ b/core/client/app/components/gh-profile-image.js @@ -16,6 +16,7 @@ import Ember from 'ember'; */ export default Ember.Component.extend({ email: '', + validEmail: '', size: 90, hasUploadedImage: false, fileStorage: true, @@ -28,12 +29,21 @@ export default Ember.Component.extend({ return `background-image: url(${url})`.htmlSafe(); }), - imageBackground: Ember.computed('email', 'size', function () { - var email = this.get('email'), + trySetValidEmail: function () { + var email = this.get('email'); + this.set('validEmail', validator.isEmail(email) ? email : ''); + }, + + didReceiveAttrs: function () { + Ember.run.debounce(this, 'trySetValidEmail', 500); + }, + + imageBackground: Ember.computed('validEmail', 'size', function () { + var email = this.get('validEmail'), size = this.get('size'), url; if (email) { - url = 'http://www.gravatar.com/avatar/' + md5(email) + '?s=' + size + '&d=blank'; + url = `http://www.gravatar.com/avatar/${md5(email)}?s=${size}&d=blank`; return `background-image: url(${url})`.htmlSafe(); } }), diff --git a/core/client/app/controllers/setup/two.js b/core/client/app/controllers/setup/two.js index 70d3834bea..b4a75d6799 100644 --- a/core/client/app/controllers/setup/two.js +++ b/core/client/app/controllers/setup/two.js @@ -7,7 +7,6 @@ export default Ember.Controller.extend(ValidationEngine, { blogTitle: null, name: null, email: '', - validEmail: '', password: null, image: null, blogCreated: false, @@ -53,9 +52,6 @@ export default Ember.Controller.extend(ValidationEngine, { preValidate: function (model) { // Only triggers validation if a value has been entered, preventing empty errors on focusOut if (this.get(model)) { - if (model === 'email') { - this.send('handleEmail'); - } this.validate({property: model}); } }, @@ -119,13 +115,6 @@ export default Ember.Controller.extend(ValidationEngine, { }, setImage: function (image) { this.set('image', image); - }, - handleEmail: function () { - var self = this; - - this.validate({property: 'email'}).then(function () { - self.set('validEmail', self.get('email')); - }); } } }); diff --git a/core/client/app/controllers/signup.js b/core/client/app/controllers/signup.js index f951123977..5f4ad6a929 100644 --- a/core/client/app/controllers/signup.js +++ b/core/client/app/controllers/signup.js @@ -9,7 +9,6 @@ export default Ember.Controller.extend(ValidationEngine, { submitting: false, flowErrors: '', image: null, - validEmail: '', ghostPaths: Ember.inject.service('ghost-paths'), config: Ember.inject.service(), @@ -88,13 +87,6 @@ export default Ember.Controller.extend(ValidationEngine, { }, setImage: function (image) { this.set('image', image); - }, - handleEmail: function () { - var self = this; - - this.validate({property: 'email'}).then(function () { - self.set('validEmail', self.get('email')); - }); } } }); diff --git a/core/client/app/templates/setup/two.hbs b/core/client/app/templates/setup/two.hbs index f79c9dfa6c..8c53c6b02f 100644 --- a/core/client/app/templates/setup/two.hbs +++ b/core/client/app/templates/setup/two.hbs @@ -7,7 +7,7 @@ - {{gh-profile-image fileStorage=config.fileStorage email=validEmail setImage="setImage"}} + {{gh-profile-image fileStorage=config.fileStorage email=email setImage="setImage"}} {{#gh-form-group errors=errors hasValidated=hasValidated property="email"}} diff --git a/core/client/app/templates/signup.hbs b/core/client/app/templates/signup.hbs index 15351c0cc1..c30e91bfc3 100644 --- a/core/client/app/templates/signup.hbs +++ b/core/client/app/templates/signup.hbs @@ -11,11 +11,11 @@ - {{gh-profile-image fileStorage=config.fileStorage email=validEmail setImage="setImage"}} + {{gh-profile-image fileStorage=config.fileStorage email=model.email setImage="setImage"}} {{#gh-form-group errors=model.errors hasValidated=hasValidated property="email"}} - {{gh-input type="email" name="email" placeholder="Eg. john@example.com" enter=(action "signup") disabled="disabled" autocorrect="off" value=model.email focusOut=(action "handleEmail")}} + {{gh-input type="email" name="email" placeholder="Eg. john@example.com" enter=(action "signup") disabled="disabled" autocorrect="off" value=model.email focusOut=(action "validate" "email")}} {{gh-error-message errors=model.errors property="email"}} {{/gh-form-group}} diff --git a/core/client/tests/unit/components/gh-profile-image-test.js b/core/client/tests/unit/components/gh-profile-image-test.js index 9665aab5b8..689ba4df75 100644 --- a/core/client/tests/unit/components/gh-profile-image-test.js +++ b/core/client/tests/unit/components/gh-profile-image-test.js @@ -28,7 +28,10 @@ describeComponent( testEmail = 'test@example.com', style, size; - component.set('email', testEmail); + Ember.run(function () { + component.set('email', testEmail); + }); + this.render(); size = component.get('size'); From de30a0c0cade0e432ee073d7a3541833fbfabc10 Mon Sep 17 00:00:00 2001 From: Kevin Ansfield Date: Wed, 28 Oct 2015 08:50:55 +0000 Subject: [PATCH 2/3] deps: ember-mocha@0.8.6 --- core/client/bower.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/client/bower.json b/core/client/bower.json index d3463f60ff..1e9bd5c940 100644 --- a/core/client/bower.json +++ b/core/client/bower.json @@ -8,7 +8,7 @@ "ember-cli-shims": "ember-cli/ember-cli-shims#0.0.3", "ember-cli-test-loader": "0.1.3", "ember-data": "1.13.13", - "ember-mocha": "0.8.4", + "ember-mocha": "0.8.6", "ember-load-initializers": "ember-cli/ember-load-initializers#0.1.5", "ember-resolver": "0.1.18", "fastclick": "1.0.6", From 2a30f919d9a515abd88951b64333668e297122a7 Mon Sep 17 00:00:00 2001 From: Kevin Ansfield Date: Mon, 26 Oct 2015 11:48:38 +0000 Subject: [PATCH 3/3] Finish changes in #5807 (debounced gravatar load in gh-profile-image) refs #5807, #5797 - add configurable debounce period - rename `hasEmail` to `displayGravatar` to better reflect it's purpose - add tests --- .../client/app/components/gh-profile-image.js | 57 +++++++----- .../templates/components/gh-profile-image.hbs | 2 +- .../components/gh-profile-image-test.js | 91 +++++++++++++++++++ .../unit/components/gh-profile-image-test.js | 51 ----------- 4 files changed, 126 insertions(+), 75 deletions(-) create mode 100644 core/client/tests/integration/components/gh-profile-image-test.js delete mode 100644 core/client/tests/unit/components/gh-profile-image-test.js diff --git a/core/client/app/components/gh-profile-image.js b/core/client/app/components/gh-profile-image.js index 14f2383b73..9a53faa69f 100644 --- a/core/client/app/components/gh-profile-image.js +++ b/core/client/app/components/gh-profile-image.js @@ -5,46 +5,52 @@ 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"}} + * Example: {{gh-profile-image email=controllerEmailProperty setImage="controllerActionName" debounce=500}} * - * @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 + * @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|action} setImage The string name of the action on the controller to be called when an image is added. + * @param {int} debounce Period to wait after changes to email before attempting to load gravatar + * @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: '', - validEmail: '', size: 90, + debounce: 300, + + validEmail: '', hasUploadedImage: false, fileStorage: true, ghostPaths: Ember.inject.service('ghost-paths'), - hasEmail: Ember.computed.notEmpty('email'), + displayGravatar: Ember.computed.notEmpty('validEmail'), defaultImage: Ember.computed('ghostPaths', function () { - var url = this.get('ghostPaths.url').asset('/shared/img/user-image.png'); - return `background-image: url(${url})`.htmlSafe(); + const url = this.get('ghostPaths.url').asset('/shared/img/user-image.png'); + return Ember.String.htmlSafe(`background-image: url(${url})`); }), trySetValidEmail: function () { - var email = this.get('email'); - this.set('validEmail', validator.isEmail(email) ? email : ''); + if (!this.get('isDestroyed')) { + const email = this.get('email'); + this.set('validEmail', validator.isEmail(email) ? email : ''); + } }, - didReceiveAttrs: function () { - Ember.run.debounce(this, 'trySetValidEmail', 500); + didReceiveAttrs: function (attrs) { + const timeout = parseInt(attrs.newAttrs.throttle || this.get('debounce')); + Ember.run.debounce(this, 'trySetValidEmail', timeout); }, imageBackground: Ember.computed('validEmail', 'size', function () { - var email = this.get('validEmail'), - size = this.get('size'), - url; + const email = this.get('validEmail'), + size = this.get('size'); + if (email) { - url = `http://www.gravatar.com/avatar/${md5(email)}?s=${size}&d=blank`; - return `background-image: url(${url})`.htmlSafe(); + let url = `http://www.gravatar.com/avatar/${md5(email)}?s=${size}&d=blank`; + return Ember.String.htmlSafe(`background-image: url(${url})`); } }), @@ -52,6 +58,9 @@ export default Ember.Component.extend({ var size = this.get('size'), uploadElement = this.$('.js-file-input'); + // Fire this immediately in case we're initialized with a valid email + this.trySetValidEmail(); + // 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 @@ -69,11 +78,13 @@ export default Ember.Component.extend({ }, willDestroyElement: function () { - this.$('.js-file-input').fileupload('destroy'); + if (this.$('.js-file-input').data()['blueimp-fileupload']) { + this.$('.js-file-input').fileupload('destroy'); + } }, queueFile: function (e, data) { - var fileName = data.files[0].name; + const fileName = data.files[0].name; if ((/\.(gif|jpe?g|png|svg?z)$/i).test(fileName)) { this.sendAction('setImage', data); @@ -81,7 +92,7 @@ export default Ember.Component.extend({ }, triggerPreview: function (e, data) { - var file = data.files[data.index]; + const file = data.files[data.index]; if (file.preview) { this.set('hasUploadedImage', true); // necessary jQuery code because file.preview is a raw DOM object diff --git a/core/client/app/templates/components/gh-profile-image.hbs b/core/client/app/templates/components/gh-profile-image.hbs index bf8c1889ba..876767bab6 100644 --- a/core/client/app/templates/components/gh-profile-image.hbs +++ b/core/client/app/templates/components/gh-profile-image.hbs @@ -2,7 +2,7 @@ {{#unless hasUploadedImage}}
- {{#if hasEmail}} + {{#if displayGravatar}}
User image
diff --git a/core/client/tests/integration/components/gh-profile-image-test.js b/core/client/tests/integration/components/gh-profile-image-test.js new file mode 100644 index 0000000000..fa335961a4 --- /dev/null +++ b/core/client/tests/integration/components/gh-profile-image-test.js @@ -0,0 +1,91 @@ +/* jshint expr:true */ +/* global md5 */ +import { expect } from 'chai'; +import { + describeComponent, + it +} from 'ember-mocha'; +import hbs from 'htmlbars-inline-precompile'; +import Ember from 'ember'; + +const {run} = Ember, + pathsStub = Ember.Service.extend({ + url: { + api: function () { + return ''; + }, + asset: function (src) { + return src; + } + } + }); + +describeComponent( + 'gh-profile-image', + 'Integration: Component: gh-profile-image', + { + integration: true + }, + function () { + beforeEach(function () { + this.register('service:ghost-paths', pathsStub); + this.inject.service('ghost-paths', {as: 'ghost-paths'}); + }); + + it('renders', function () { + this.set('email', ''); + + this.render(hbs` + {{gh-profile-image email=email}} + `); + + expect(this.$()).to.have.length(1); + }); + + it('immediately renders the gravatar if valid email supplied', function () { + let email = 'test@example.com', + expectedUrl = `http://www.gravatar.com/avatar/${md5(email)}?s=100&d=blank`; + + this.set('email', email); + + this.render(hbs` + {{gh-profile-image email=email size=100 debounce=300}} + `); + + expect(this.$('.gravatar-img').attr('style'), 'gravatar image style') + .to.equal(`background-image: url(${expectedUrl})`); + }); + + it('throttles gravatar loading as email is changed', function (done) { + let email = 'test@example.com', + expectedUrl = `http://www.gravatar.com/avatar/${md5(email)}?s=100&d=blank`; + + this.set('email', 'test'); + + this.render(hbs` + {{gh-profile-image email=email size=100 debounce=300}} + `); + + expect(this.$('.gravatar-img').length, '.gravatar-img not shown for invalid email') + .to.equal(0); + + run(() => { + this.set('email', email); + }); + + expect(this.$('.gravatar-img').length, '.gravatar-img not immediately changed on email change') + .to.equal(0); + + Ember.run.later(this, function () { + expect(this.$('.gravatar-img').length, '.gravatar-img still not shown before throttle timeout') + .to.equal(0); + }, 250); + + Ember.run.later(this, function () { + expect(this.$('.gravatar-img').attr('style'), '.gravatar-img style after timeout') + .to.equal(`background-image: url(${expectedUrl})`); + done(); + }, 400); + }); + } +); diff --git a/core/client/tests/unit/components/gh-profile-image-test.js b/core/client/tests/unit/components/gh-profile-image-test.js deleted file mode 100644 index 689ba4df75..0000000000 --- a/core/client/tests/unit/components/gh-profile-image-test.js +++ /dev/null @@ -1,51 +0,0 @@ -/* jshint expr:true */ -/* global md5 */ -import { expect } from 'chai'; -import { - describeComponent, - it -} from 'ember-mocha'; - -describeComponent( - 'gh-profile-image', - 'Unit: Component: gh-profile-image', - { - unit: true, - 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; - - Ember.run(function () { - 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); - }); - } -);