2017-08-22 10:53:26 +03:00
import Controller from '@ember/controller' ;
2017-05-29 21:50:03 +03:00
import Ember from 'ember' ;
import boundOneWay from 'ghost-admin/utils/bound-one-way' ;
import isNumber from 'ghost-admin/utils/isNumber' ;
2017-08-22 10:53:26 +03:00
import { alias , and , not , or , readOnly } from '@ember/object/computed' ;
import { computed } from '@ember/object' ;
import { htmlSafe } from '@ember/string' ;
import { isArray as isEmberArray } from '@ember/array' ;
import { run } from '@ember/runloop' ;
2017-10-30 12:38:01 +03:00
import { inject as service } from '@ember/service' ;
2016-08-11 09:58:38 +03:00
import { task , taskGroup } from 'ember-concurrency' ;
2017-01-04 18:52:00 +03:00
// ember-cli-shims doesn't export this
const { Handlebars } = Ember ;
2016-04-25 12:54:36 +03:00
export default Controller . extend ( {
2017-10-31 18:27:25 +03:00
leaveSettingsTransition : null ,
dirtyAttributes : false ,
2015-11-18 13:50:48 +03:00
showDeleteUserModal : false ,
2017-03-08 21:21:35 +03:00
showSuspendUserModal : false ,
2015-11-18 13:50:48 +03:00
showTransferOwnerModal : false ,
showUploadCoverModal : false ,
showUplaodImageModal : false ,
2016-03-03 11:52:27 +03:00
_scratchFacebook : null ,
_scratchTwitter : null ,
2014-07-31 08:25:42 +04:00
2017-10-30 12:38:01 +03:00
ajax : service ( ) ,
config : service ( ) ,
dropdown : service ( ) ,
ghostPaths : service ( ) ,
notifications : service ( ) ,
session : service ( ) ,
slugGenerator : service ( ) ,
2015-10-28 14:36:45 +03:00
user : alias ( 'model' ) ,
2015-11-18 13:50:48 +03:00
currentUser : alias ( 'session.user' ) ,
2014-07-01 19:58:26 +04:00
2015-11-18 13:50:48 +03:00
email : readOnly ( 'model.email' ) ,
slugValue : boundOneWay ( 'model.slug' ) ,
2014-07-02 07:44:39 +04:00
2015-10-28 14:36:45 +03:00
canAssignRoles : or ( 'currentUser.isAdmin' , 'currentUser.isOwner' ) ,
2017-09-04 22:17:04 +03:00
canChangeEmail : not ( 'isAdminUserOnOwnerProfile' ) ,
canChangePassword : not ( 'isAdminUserOnOwnerProfile' ) ,
2015-10-28 14:36:45 +03:00
canMakeOwner : and ( 'currentUser.isOwner' , 'isNotOwnProfile' , 'user.isAdmin' ) ,
2017-09-04 22:17:04 +03:00
isAdminUserOnOwnerProfile : and ( 'currentUser.isAdmin' , 'user.isOwner' ) ,
isNotOwnersProfile : not ( 'user.isOwner' ) ,
2015-10-28 14:36:45 +03:00
rolesDropdownIsVisible : and ( 'isNotOwnProfile' , 'canAssignRoles' , 'isNotOwnersProfile' ) ,
2015-11-18 13:50:48 +03:00
userActionsAreVisible : or ( 'deleteUserActionIsVisible' , 'canMakeOwner' ) ,
2017-03-03 16:24:43 +03:00
isOwnProfile : computed ( 'user.id' , 'currentUser.id' , function ( ) {
return this . get ( 'user.id' ) === this . get ( 'currentUser.id' ) ;
} ) ,
isNotOwnProfile : not ( 'isOwnProfile' ) ,
2015-05-26 05:10:50 +03:00
2015-10-28 14:36:45 +03:00
deleteUserActionIsVisible : computed ( 'currentUser' , 'canAssignRoles' , 'user' , function ( ) {
2016-11-14 16:16:51 +03:00
if ( ( this . get ( 'canAssignRoles' ) && this . get ( 'isNotOwnProfile' ) && ! this . get ( 'user.isOwner' ) )
|| ( this . get ( 'currentUser.isEditor' ) && ( this . get ( 'isNotOwnProfile' )
|| this . get ( 'user.isAuthor' ) ) ) ) {
2015-06-13 17:34:09 +03:00
return true ;
}
2014-07-30 05:57:19 +04:00
} ) ,
2014-03-23 06:31:45 +04:00
2015-06-13 17:34:09 +03:00
// duplicated in gh-user-active -- find a better home and consolidate?
2015-10-28 14:36:45 +03:00
userDefault : computed ( 'ghostPaths' , function ( ) {
2016-10-21 11:44:25 +03:00
return ` ${ this . get ( 'ghostPaths.assetRoot' ) } /img/user-image.png ` ;
2014-07-30 05:57:19 +04:00
} ) ,
2015-05-26 05:10:50 +03:00
2017-04-24 20:22:39 +03:00
userImageBackground : computed ( 'user.profileImage' , 'userDefault' , function ( ) {
let url = this . get ( 'user.profileImage' ) || this . get ( 'userDefault' ) ;
2017-01-04 18:52:00 +03:00
let safeUrl = Handlebars . Utils . escapeExpression ( url ) ;
2015-05-26 05:10:50 +03:00
2017-01-04 18:52:00 +03:00
return htmlSafe ( ` background-image: url( ${ safeUrl } ) ` ) ;
2014-07-30 05:57:19 +04:00
} ) ,
2015-06-13 17:34:09 +03:00
// end duplicated
2014-07-16 21:12:45 +04:00
2015-10-28 14:36:45 +03:00
coverDefault : computed ( 'ghostPaths' , function ( ) {
2016-10-21 11:44:25 +03:00
return ` ${ this . get ( 'ghostPaths.assetRoot' ) } /img/user-cover.png ` ;
2014-07-30 05:57:19 +04:00
} ) ,
2014-07-02 07:44:39 +04:00
2017-04-24 20:22:39 +03:00
coverImageBackground : computed ( 'user.coverImage' , 'coverDefault' , function ( ) {
let url = this . get ( 'user.coverImage' ) || this . get ( 'coverDefault' ) ;
2017-01-04 18:52:00 +03:00
let safeUrl = Handlebars . Utils . escapeExpression ( url ) ;
2015-06-13 17:34:09 +03:00
2017-01-04 18:52:00 +03:00
return htmlSafe ( ` background-image: url( ${ safeUrl } ) ` ) ;
2015-06-13 17:34:09 +03:00
} ) ,
2014-07-16 21:12:45 +04:00
2015-10-28 14:36:45 +03:00
coverTitle : computed ( 'user.name' , function ( ) {
return ` ${ this . get ( 'user.name' ) } 's Cover Image ` ;
2014-07-30 05:57:19 +04:00
} ) ,
2014-07-31 08:25:42 +04:00
2015-10-28 14:36:45 +03:00
roles : computed ( function ( ) {
2015-09-03 14:06:50 +03:00
return this . store . query ( 'role' , { permissions : 'assign' } ) ;
2015-07-09 20:10:00 +03:00
} ) ,
2015-11-18 13:50:48 +03:00
_deleteUser ( ) {
if ( this . get ( 'deleteUserActionIsVisible' ) ) {
let user = this . get ( 'user' ) ;
return user . destroyRecord ( ) ;
}
} ,
_deleteUserSuccess ( ) {
this . get ( 'notifications' ) . closeAlerts ( 'user.delete' ) ;
this . store . unloadAll ( 'post' ) ;
this . transitionToRoute ( 'team' ) ;
} ,
_deleteUserFailure ( ) {
this . get ( 'notifications' ) . showAlert ( 'The user could not be deleted. Please try again.' , { type : 'error' , key : 'user.delete.failed' } ) ;
} ,
2016-08-11 09:58:38 +03:00
saveHandlers : taskGroup ( ) . enqueue ( ) ,
2014-10-25 01:09:50 +04:00
2016-08-11 09:58:38 +03:00
updateSlug : task ( function * ( newSlug ) {
2017-10-31 18:27:25 +03:00
let slug = this . get ( 'user.slug' ) ;
2016-08-11 09:58:38 +03:00
newSlug = newSlug || slug ;
newSlug = newSlug . trim ( ) ;
// Ignore unchanged slugs or candidate slugs that are empty
if ( ! newSlug || slug === newSlug ) {
this . set ( 'slugValue' , slug ) ;
2017-08-22 10:59:49 +03:00
return true ;
2016-08-11 09:58:38 +03:00
}
let serverSlug = yield this . get ( 'slugGenerator' ) . generateSlug ( 'user' , newSlug ) ;
// If after getting the sanitized and unique slug back from the API
// we end up with a slug that matches the existing slug, abort the change
if ( serverSlug === slug ) {
2017-08-22 10:59:49 +03:00
return true ;
2016-08-11 09:58:38 +03:00
}
// Because the server transforms the candidate slug by stripping
// certain characters and appending a number onto the end of slugs
// to enforce uniqueness, there are cases where we can get back a
// candidate slug that is a duplicate of the original except for
// the trailing incrementor (e.g., this-is-a-slug and this-is-a-slug-2)
// get the last token out of the slug candidate and see if it's a number
let slugTokens = serverSlug . split ( '-' ) ;
let check = Number ( slugTokens . pop ( ) ) ;
// if the candidate slug is the same as the existing slug except
// for the incrementor then the existing slug should be used
if ( isNumber ( check ) && check > 0 ) {
if ( slug === slugTokens . join ( '-' ) && serverSlug !== newSlug ) {
this . set ( 'slugValue' , slug ) ;
2017-08-22 10:59:49 +03:00
return true ;
2014-08-12 22:56:31 +04:00
}
2016-08-11 09:58:38 +03:00
}
2014-08-12 22:56:31 +04:00
2016-08-11 09:58:38 +03:00
this . set ( 'slugValue' , serverSlug ) ;
2017-10-31 18:27:25 +03:00
this . set ( 'dirtyAttributes' , true ) ;
2017-08-22 10:59:49 +03:00
return true ;
2016-08-11 09:58:38 +03:00
} ) . group ( 'saveHandlers' ) ,
2015-08-10 18:43:49 +03:00
2016-08-11 09:58:38 +03:00
save : task ( function * ( ) {
let user = this . get ( 'user' ) ;
let slugValue = this . get ( 'slugValue' ) ;
let slugChanged ;
2014-08-12 22:56:31 +04:00
2016-08-11 09:58:38 +03:00
if ( user . get ( 'slug' ) !== slugValue ) {
slugChanged = true ;
user . set ( 'slug' , slugValue ) ;
}
2014-08-12 22:56:31 +04:00
2016-08-11 09:58:38 +03:00
try {
let model = yield user . save ( { format : false } ) ;
let currentPath ,
newPath ;
2014-08-12 22:56:31 +04:00
2016-08-11 09:58:38 +03:00
// If the user's slug has changed, change the URL and replace
// the history so refresh and back button still work
if ( slugChanged ) {
2017-06-08 20:04:16 +03:00
currentPath = window . location . hash ;
2014-08-12 22:56:31 +04:00
2016-08-11 09:58:38 +03:00
newPath = currentPath . split ( '/' ) ;
2017-06-08 20:04:16 +03:00
newPath [ newPath . length - 1 ] = model . get ( 'slug' ) ;
2016-08-11 09:58:38 +03:00
newPath = newPath . join ( '/' ) ;
2015-08-10 18:43:49 +03:00
2016-08-11 09:58:38 +03:00
window . history . replaceState ( { path : newPath } , '' , newPath ) ;
}
2014-08-12 22:56:31 +04:00
2017-10-31 18:27:25 +03:00
this . set ( 'dirtyAttributes' , false ) ;
2016-08-11 09:58:38 +03:00
this . get ( 'notifications' ) . closeAlerts ( 'user.update' ) ;
return model ;
} catch ( error ) {
// validation engine returns undefined so we have to check
// before treating the failure as an API error
if ( error ) {
this . get ( 'notifications' ) . showAPIError ( error , { key : 'user.update' } ) ;
}
}
} ) . group ( 'saveHandlers' ) ,
actions : {
changeRole ( newRole ) {
2017-10-31 18:27:25 +03:00
this . get ( 'user' ) . set ( 'role' , newRole ) ;
this . set ( 'dirtyAttributes' , true ) ;
2014-03-23 06:31:45 +04:00
} ,
2015-11-18 13:50:48 +03:00
deleteUser ( ) {
return this . _deleteUser ( ) . then ( ( ) => {
this . _deleteUserSuccess ( ) ;
} , ( ) => {
this . _deleteUserFailure ( ) ;
} ) ;
} ,
toggleDeleteUserModal ( ) {
if ( this . get ( 'deleteUserActionIsVisible' ) ) {
this . toggleProperty ( 'showDeleteUserModal' ) ;
}
} ,
2017-03-08 21:21:35 +03:00
suspendUser ( ) {
2017-10-31 18:27:25 +03:00
this . get ( 'user' ) . set ( 'status' , 'inactive' ) ;
2017-03-08 21:21:35 +03:00
return this . get ( 'save' ) . perform ( ) ;
} ,
toggleSuspendUserModal ( ) {
if ( this . get ( 'deleteUserActionIsVisible' ) ) {
this . toggleProperty ( 'showSuspendUserModal' ) ;
}
} ,
unsuspendUser ( ) {
2017-10-31 18:27:25 +03:00
this . get ( 'user' ) . set ( 'status' , 'active' ) ;
2017-03-08 21:21:35 +03:00
return this . get ( 'save' ) . perform ( ) ;
} ,
toggleUnsuspendUserModal ( ) {
if ( this . get ( 'deleteUserActionIsVisible' ) ) {
this . toggleProperty ( 'showUnsuspendUserModal' ) ;
}
} ,
2016-03-03 11:52:27 +03:00
validateFacebookUrl ( ) {
let newUrl = this . get ( '_scratchFacebook' ) ;
let oldUrl = this . get ( 'user.facebook' ) ;
let errMessage = '' ;
2016-05-17 21:14:14 +03:00
if ( newUrl === '' ) {
2016-03-03 11:52:27 +03:00
// Clear out the Facebook url
this . set ( 'user.facebook' , '' ) ;
this . get ( 'user.errors' ) . remove ( 'facebook' ) ;
return ;
}
2016-05-17 21:14:14 +03:00
// _scratchFacebook will be null unless the user has input something
if ( ! newUrl ) {
newUrl = oldUrl ;
}
2016-03-03 11:52:27 +03:00
// If new url didn't change, exit
if ( newUrl === oldUrl ) {
2016-05-16 21:16:40 +03:00
this . get ( 'user.errors' ) . remove ( 'facebook' ) ;
2016-03-03 11:52:27 +03:00
return ;
}
// TODO: put the validation here into a validator
2017-09-11 10:56:11 +03:00
if ( newUrl . match ( /(?:facebook\.com\/)(\S+)/ ) || newUrl . match ( /([a-z\d.]+)/i ) ) {
2016-05-16 21:16:40 +03:00
let username = [ ] ;
2016-03-03 11:52:27 +03:00
2016-05-16 21:16:40 +03:00
if ( newUrl . match ( /(?:facebook\.com\/)(\S+)/ ) ) {
2016-11-14 16:16:51 +03:00
[ , username ] = newUrl . match ( /(?:facebook\.com\/)(\S+)/ ) ;
2016-05-16 21:16:40 +03:00
} else {
2017-09-11 10:56:11 +03:00
[ , username ] = newUrl . match ( /(?:https:\/\/|http:\/\/)?(?:www\.)?(?:\w+\.\w+\/+)?(\S+)/mi ) ;
2016-05-16 21:16:40 +03:00
}
2016-03-03 11:52:27 +03:00
2016-05-16 21:16:40 +03:00
// check if we have a /page/username or without
if ( username . match ( /^(?:\/)?(pages?\/\S+)/mi ) ) {
// we got a page url, now save the username without the / in the beginning
2016-11-14 16:16:51 +03:00
[ , username ] = username . match ( /^(?:\/)?(pages?\/\S+)/mi ) ;
2017-09-11 10:56:11 +03:00
} else if ( username . match ( /^(http|www)|(\/)/ ) || ! username . match ( /^([a-z\d.]{1,50})$/mi ) ) {
errMessage = ! username . match ( /^([a-z\d.]{1,50})$/mi ) ? 'Your Username is not a valid Facebook Username' : 'The URL must be in a format like https://www.facebook.com/yourUsername' ;
2016-03-03 11:52:27 +03:00
this . get ( 'user.errors' ) . add ( 'facebook' , errMessage ) ;
this . get ( 'user.hasValidated' ) . pushObject ( 'facebook' ) ;
return ;
}
2016-05-16 21:16:40 +03:00
newUrl = ` https://www.facebook.com/ ${ username } ` ;
this . set ( 'user.facebook' , newUrl ) ;
this . get ( 'user.errors' ) . remove ( 'facebook' ) ;
this . get ( 'user.hasValidated' ) . pushObject ( 'facebook' ) ;
2017-10-31 18:27:25 +03:00
// necessary to update the value in the input field
this . set ( 'user.facebook' , '' ) ;
run . schedule ( 'afterRender' , this , function ( ) {
this . set ( 'user.facebook' , newUrl ) ;
2016-05-16 21:16:40 +03:00
} ) ;
} else {
2016-11-14 16:16:51 +03:00
errMessage = 'The URL must be in a format like '
+ 'https://www.facebook.com/yourUsername' ;
2016-05-16 21:16:40 +03:00
this . get ( 'user.errors' ) . add ( 'facebook' , errMessage ) ;
this . get ( 'user.hasValidated' ) . pushObject ( 'facebook' ) ;
return ;
2016-03-03 11:52:27 +03:00
}
} ,
validateTwitterUrl ( ) {
let newUrl = this . get ( '_scratchTwitter' ) ;
let oldUrl = this . get ( 'user.twitter' ) ;
let errMessage = '' ;
2016-05-17 21:14:14 +03:00
if ( newUrl === '' ) {
2016-03-03 11:52:27 +03:00
// Clear out the Twitter url
this . set ( 'user.twitter' , '' ) ;
this . get ( 'user.errors' ) . remove ( 'twitter' ) ;
return ;
}
2016-05-17 21:14:14 +03:00
// _scratchTwitter will be null unless the user has input something
if ( ! newUrl ) {
newUrl = oldUrl ;
}
2016-03-03 11:52:27 +03:00
// If new url didn't change, exit
if ( newUrl === oldUrl ) {
2016-05-16 21:16:40 +03:00
this . get ( 'user.errors' ) . remove ( 'twitter' ) ;
2016-03-03 11:52:27 +03:00
return ;
}
// TODO: put the validation here into a validator
2017-09-11 10:56:11 +03:00
if ( newUrl . match ( /(?:twitter\.com\/)(\S+)/ ) || newUrl . match ( /([a-z\d.]+)/i ) ) {
2016-05-16 21:16:40 +03:00
let username = [ ] ;
2016-03-03 11:52:27 +03:00
2016-05-16 21:16:40 +03:00
if ( newUrl . match ( /(?:twitter\.com\/)(\S+)/ ) ) {
2016-11-14 16:16:51 +03:00
[ , username ] = newUrl . match ( /(?:twitter\.com\/)(\S+)/ ) ;
2016-05-16 21:16:40 +03:00
} else {
[ username ] = newUrl . match ( /([^/]+)\/?$/mi ) ;
}
2016-03-03 11:52:27 +03:00
2016-05-16 21:16:40 +03:00
// check if username starts with http or www and show error if so
2017-09-11 10:56:11 +03:00
if ( username . match ( /^(http|www)|(\/)/ ) || ! username . match ( /^[a-z\d._]{1,15}$/mi ) ) {
errMessage = ! username . match ( /^[a-z\d._]{1,15}$/mi ) ? 'Your Username is not a valid Twitter Username' : 'The URL must be in a format like https://twitter.com/yourUsername' ;
2016-03-03 11:52:27 +03:00
this . get ( 'user.errors' ) . add ( 'twitter' , errMessage ) ;
this . get ( 'user.hasValidated' ) . pushObject ( 'twitter' ) ;
return ;
}
2016-05-16 21:16:40 +03:00
newUrl = ` https://twitter.com/ ${ username } ` ;
this . set ( 'user.twitter' , newUrl ) ;
this . get ( 'user.errors' ) . remove ( 'twitter' ) ;
this . get ( 'user.hasValidated' ) . pushObject ( 'twitter' ) ;
2017-10-31 18:27:25 +03:00
// necessary to update the value in the input field
this . set ( 'user.twitter' , '' ) ;
run . schedule ( 'afterRender' , this , function ( ) {
this . set ( 'user.twitter' , newUrl ) ;
2016-05-16 21:16:40 +03:00
} ) ;
} else {
2016-11-14 16:16:51 +03:00
errMessage = 'The URL must be in a format like '
+ 'https://twitter.com/yourUsername' ;
2016-05-16 21:16:40 +03:00
this . get ( 'user.errors' ) . add ( 'twitter' , errMessage ) ;
this . get ( 'user.hasValidated' ) . pushObject ( 'twitter' ) ;
return ;
2016-03-03 11:52:27 +03:00
}
} ,
2015-11-18 13:50:48 +03:00
transferOwnership ( ) {
let user = this . get ( 'user' ) ;
let url = this . get ( 'ghostPaths.url' ) . api ( 'users' , 'owner' ) ;
this . get ( 'dropdown' ) . closeDropdowns ( ) ;
2016-01-18 18:37:14 +03:00
return this . get ( 'ajax' ) . put ( url , {
2015-11-18 13:50:48 +03:00
data : {
owner : [ {
id : user . get ( 'id' )
} ]
}
} ) . then ( ( response ) => {
// manually update the roles for the users that just changed roles
// because store.pushPayload is not working with embedded relations
2016-07-06 22:47:30 +03:00
if ( response && isEmberArray ( response . users ) ) {
2015-11-18 13:50:48 +03:00
response . users . forEach ( ( userJSON ) => {
let user = this . store . peekRecord ( 'user' , userJSON . id ) ;
let role = this . store . peekRecord ( 'role' , userJSON . roles [ 0 ] . id ) ;
user . set ( 'role' , role ) ;
} ) ;
}
this . get ( 'notifications' ) . showAlert ( ` Ownership successfully transferred to ${ user . get ( 'name' ) } ` , { type : 'success' , key : 'owner.transfer.success' } ) ;
} ) . catch ( ( error ) => {
this . get ( 'notifications' ) . showAPIError ( error , { key : 'owner.transfer' } ) ;
} ) ;
} ,
2017-10-31 18:27:25 +03:00
toggleLeaveSettingsModal ( transition ) {
let leaveTransition = this . get ( 'leaveSettingsTransition' ) ;
if ( ! transition && this . get ( 'showLeaveSettingsModal' ) ) {
this . set ( 'leaveSettingsTransition' , null ) ;
this . set ( 'showLeaveSettingsModal' , false ) ;
return ;
}
if ( ! leaveTransition || transition . targetName === leaveTransition . targetName ) {
this . set ( 'leaveSettingsTransition' , transition ) ;
// if a save is running, wait for it to finish then transition
if ( this . get ( 'saveHandlers.isRunning' ) ) {
return this . get ( 'saveHandlers.last' ) . then ( ( ) => {
transition . retry ( ) ;
} ) ;
}
// we genuinely have unsaved data, show the modal
this . set ( 'showLeaveSettingsModal' , true ) ;
}
} ,
leaveSettings ( ) {
let transition = this . get ( 'leaveSettingsTransition' ) ;
let user = this . get ( 'user' ) ;
if ( ! transition ) {
this . get ( 'notifications' ) . showAlert ( 'Sorry, there was an error in the application. Please let the Ghost team know what happened.' , { type : 'error' } ) ;
return ;
}
// roll back changes on model props
user . rollbackAttributes ( ) ;
// roll back the slugValue property
if ( this . get ( 'dirtyAttributes' ) ) {
this . set ( 'slugValue' , user . get ( 'slug' ) ) ;
this . set ( 'dirtyAttributes' , false ) ;
}
return transition . retry ( ) ;
} ,
2015-11-18 13:50:48 +03:00
toggleTransferOwnerModal ( ) {
if ( this . get ( 'canMakeOwner' ) ) {
this . toggleProperty ( 'showTransferOwnerModal' ) ;
}
} ,
toggleUploadCoverModal ( ) {
this . toggleProperty ( 'showUploadCoverModal' ) ;
} ,
toggleUploadImageModal ( ) {
this . toggleProperty ( 'showUploadImageModal' ) ;
2016-06-14 14:46:24 +03:00
} ,
// TODO: remove those mutation actions once we have better
// inline validations that auto-clear errors on input
updatePassword ( password ) {
this . set ( 'user.password' , password ) ;
this . get ( 'user.hasValidated' ) . removeObject ( 'password' ) ;
this . get ( 'user.errors' ) . remove ( 'password' ) ;
} ,
updateNewPassword ( password ) {
this . set ( 'user.newPassword' , password ) ;
this . get ( 'user.hasValidated' ) . removeObject ( 'newPassword' ) ;
this . get ( 'user.errors' ) . remove ( 'newPassword' ) ;
} ,
updateNe2Password ( password ) {
this . set ( 'user.ne2Password' , password ) ;
this . get ( 'user.hasValidated' ) . removeObject ( 'ne2Password' ) ;
this . get ( 'user.errors' ) . remove ( 'ne2Password' ) ;
2014-03-23 06:31:45 +04:00
}
}
} ) ;