2014-07-17 12:12:21 -04:00
RootView = require ' views/kinds/RootView '
2014-01-03 13:32:13 -05:00
template = require ' templates/account/profile '
User = require ' models/User '
2014-06-11 22:38:41 -04:00
LevelSession = require ' models/LevelSession '
CocoCollection = require ' collections/CocoCollection '
2014-06-07 14:45:49 -04:00
{ me } = require ' lib/auth '
2014-07-23 10:02:45 -04:00
JobProfileContactModal = require ' views/modal/JobProfileContactModal '
JobProfileTreemaView = require ' views/account/JobProfileTreemaView '
2014-06-17 16:03:08 -04:00
UserRemark = require ' models/UserRemark '
2014-06-01 19:45:19 -04:00
forms = require ' lib/forms '
2014-07-23 10:02:45 -04:00
ModelModal = require ' views/modal/ModelModal '
2014-07-17 12:12:21 -04:00
JobProfileCodeModal = require ' ./JobProfileCodeModal '
2014-01-03 13:32:13 -05:00
2014-06-11 22:38:41 -04:00
class LevelSessionsCollection extends CocoCollection
url: -> " /db/user/ #{ @ userID } /level.sessions/employer "
model: LevelSession
constructor: (@userID) ->
super ( )
2014-06-17 16:03:08 -04:00
adminContacts = [
2014-06-30 22:16:26 -04:00
{ id: ' ' , name: ' Assign a Contact ' }
{ id: ' 512ef4805a67a8c507000001 ' , name: ' Nick ' }
{ id: ' 5162fab9c92b4c751e000274 ' , name: ' Scott ' }
{ id: ' 51eb2714fa058cb20d0006ef ' , name: ' Michael ' }
{ id: ' 51538fdb812dd9af02000001 ' , name: ' George ' }
{ id: ' 52a57252a89409700d0000d9 ' , name: ' Ignore ' }
2014-06-17 16:03:08 -04:00
]
2014-07-23 10:02:45 -04:00
module.exports = class JobProfileView extends RootView
2014-06-30 22:16:26 -04:00
id: ' profile-view '
2014-01-03 13:32:13 -05:00
template: template
2014-07-17 12:12:21 -04:00
showBackground: false
2014-06-07 14:45:49 -04:00
subscriptions:
' linkedin-loaded ' : ' onLinkedInLoaded '
2014-01-03 13:32:13 -05:00
2014-04-07 20:58:02 -04:00
events:
2014-05-31 01:12:44 -04:00
' click # toggle-editing ' : ' toggleEditing '
2014-06-07 14:45:49 -04:00
' click # importLinkedIn ' : ' importLinkedIn '
2014-06-01 01:09:41 -04:00
' click # toggle-job-profile-active ' : ' toggleJobProfileActive '
2014-04-07 20:58:02 -04:00
' click # toggle-job-profile-approved ' : ' toggleJobProfileApproved '
2014-06-07 19:33:14 -04:00
' click # save-notes-button ' : ' onJobProfileNotesChanged '
2014-04-10 17:59:32 -04:00
' click # contact-candidate ' : ' onContactCandidate '
2014-04-25 19:57:42 -04:00
' click # enter-espionage-mode ' : ' enterEspionageMode '
2014-06-17 18:17:19 -04:00
' click # open-model-modal ' : ' openModelModal '
2014-05-31 01:12:44 -04:00
' click .editable-profile .profile-photo ' : ' onEditProfilePhoto '
2014-06-01 01:09:41 -04:00
' click .editable-profile .project-image ' : ' onEditProjectImage '
2014-05-31 01:12:44 -04:00
' click .editable-profile .editable-display ' : ' onEditSection '
' click .editable-profile .save-section ' : ' onSaveSection '
' click .editable-profile .glyphicon-remove ' : ' onCancelSectionEdit '
' change .editable-profile .editable-array input ' : ' onEditArray '
2014-06-01 01:09:41 -04:00
' keyup .editable-profile .editable-array input ' : ' onEditArray '
' click .editable-profile a ' : ' onClickLinkWhileEditing '
2014-06-17 16:03:08 -04:00
' change # admin-contact ' : ' onAdminContactChanged '
2014-07-17 12:12:21 -04:00
' click .session-link ' : ' onSessionLinkPressed '
2014-04-07 20:58:02 -04:00
2014-01-03 13:32:13 -05:00
constructor: (options, @userID) ->
2014-06-01 19:45:19 -04:00
@ userID ? = me . id
2014-04-07 20:58:02 -04:00
@onJobProfileNotesChanged = _ . debounce @ onJobProfileNotesChanged , 1000
2014-06-17 16:03:08 -04:00
@onRemarkChanged = _ . debounce @ onRemarkChanged , 1000
2014-06-07 14:45:49 -04:00
@authorizedWithLinkedIn = IN ? . User ? . isAuthorized ( )
2014-06-17 16:03:08 -04:00
@linkedInLoaded = Boolean ( IN ? . parse )
2014-06-07 14:45:49 -04:00
@waitingForLinkedIn = false
window . contractCallback = =>
@authorizedWithLinkedIn = IN ? . User ? . isAuthorized ( )
@ render ( )
2014-01-03 13:32:13 -05:00
super options
2014-07-21 19:59:42 -04:00
if me . get ( ' anonymous ' ) is true
@ render ( )
return
2014-06-07 19:33:14 -04:00
if User . isObjectID @ userID
@ finishInit ( )
else
$ . ajax " /db/user/ #{ @ userID } /nameToID " , success: (@userID) =>
@ finishInit ( ) unless @ destroyed
@ render ( )
finishInit: ->
return unless @ userID
2014-06-01 01:09:41 -04:00
@uploadFilePath = " db/user/ #{ @ userID } "
2014-06-01 19:45:19 -04:00
@highlightedContainers = [ ]
2014-04-09 19:46:44 -04:00
if @ userID is me . id
@user = me
2014-06-30 22:16:26 -04:00
else if me . isAdmin ( ) or ' employer ' in me . get ( ' permissions ' )
2014-04-09 19:46:44 -04:00
@user = User . getByID ( @ userID )
2014-04-29 18:12:50 -04:00
@ user . fetch ( )
2014-06-30 22:16:26 -04:00
@ listenTo @ user , ' sync ' , =>
2014-04-29 18:12:50 -04:00
@ render ( )
2014-06-10 19:30:07 -04:00
$ . post " /db/user/ #{ me . id } /track/view_candidate "
$ . post " /db/user/ #{ @ userID } /track/viewed_by_employer " unless me . isAdmin ( )
2014-06-01 19:45:19 -04:00
else
@user = User . getByID ( @ userID )
2014-06-11 22:38:41 -04:00
@sessions = @ supermodel . loadCollection ( new LevelSessionsCollection ( @ userID ) , ' candidate_sessions ' ) . model
2014-06-17 16:03:08 -04:00
if me . isAdmin ( )
# Mimicking how the VictoryModal fetches LevelFeedback
@remark = new UserRemark ( )
@ remark . setURL " /db/user/ #{ @ userID } /remark "
@ remark . fetch ( )
@ listenToOnce @ remark , ' sync ' , @ onRemarkLoaded
@ listenToOnce @ remark , ' error ' , @ onRemarkNotFound
onRemarkLoaded: ->
@ remark . setURL " /db/user.remark/ #{ @ remark . id } "
@ render ( )
onRemarkNotFound: ->
@remark = new UserRemark ( ) # hmm, why do we create a new one here?
@ remark . set ' user ' , @ userID
@ remark . set ' userName ' , name if name = @ user . get ( ' name ' )
2014-01-03 13:32:13 -05:00
2014-06-07 14:45:49 -04:00
onLinkedInLoaded: =>
@linkedinLoaded = true
if @ waitingForLinkedIn
@ renderLinkedInButton ( )
2014-06-23 12:46:39 -04:00
@authorizedWithLinkedIn = IN ? . User ? . isAuthorized ( )
2014-06-07 14:45:49 -04:00
renderLinkedInButton: =>
2014-06-08 21:37:33 -04:00
IN ? . parse ( )
2014-06-07 14:45:49 -04:00
afterInsert: ->
super ( )
2014-06-30 22:16:26 -04:00
linkedInButtonParentElement = document . getElementById ( ' linkedInAuthButton ' )
2014-06-07 14:45:49 -04:00
if linkedInButtonParentElement
if @ linkedinLoaded
@ renderLinkedInButton ( )
else
@waitingForLinkedIn = true
2014-06-11 22:38:41 -04:00
2014-06-07 14:45:49 -04:00
importLinkedIn: =>
2014-06-30 22:16:26 -04:00
overwriteConfirm = confirm ( ' Importing LinkedIn data will overwrite your current work experience, skills, name, descriptions, and education. Continue? ' )
2014-06-08 20:38:02 -04:00
unless overwriteConfirm then return
2014-06-07 14:45:49 -04:00
application . linkedinHandler . getProfileData (err, profileData) =>
2014-06-08 21:21:56 -04:00
@ processLinkedInProfileData profileData
2014-06-30 22:16:26 -04:00
2014-06-08 20:38:02 -04:00
jobProfileSchema: -> @ user . schema ( ) . properties . jobProfile . properties
2014-06-08 21:37:33 -04:00
2014-06-08 21:21:56 -04:00
processLinkedInProfileData: (p) ->
2014-06-07 14:45:49 -04:00
#handle formatted-name
2014-06-08 20:38:02 -04:00
currentJobProfile = @ user . get ( ' jobProfile ' )
2014-06-08 21:15:16 -04:00
oldJobProfile = _ . cloneDeep ( currentJobProfile )
2014-06-08 20:38:02 -04:00
jobProfileSchema = @ user . schema ( ) . properties . jobProfile . properties
2014-06-08 21:37:33 -04:00
2014-06-30 22:16:26 -04:00
if p [ ' formattedName ' ] ? and p [ ' formattedName ' ] isnt ' private '
2014-06-08 20:38:02 -04:00
nameMaxLength = jobProfileSchema . name . maxLength
2014-06-30 22:16:26 -04:00
currentJobProfile.name = p [ ' formattedName ' ] . slice ( 0 , nameMaxLength )
if p [ ' skills ' ] ? [ ' values ' ] . length
2014-06-07 14:45:49 -04:00
skillNames = [ ]
2014-06-08 20:38:02 -04:00
skillMaxLength = jobProfileSchema . skills . items . maxLength
2014-06-07 14:45:49 -04:00
for skill in p . skills . values
2014-06-30 22:16:26 -04:00
skillNames . push skill . skill . name . slice ( 0 , skillMaxLength )
2014-06-07 14:45:49 -04:00
currentJobProfile.skills = skillNames
2014-06-30 22:16:26 -04:00
if p [ ' headline ' ]
2014-06-08 20:38:02 -04:00
shortDescriptionMaxLength = jobProfileSchema . shortDescription . maxLength
2014-06-30 22:16:26 -04:00
currentJobProfile.shortDescription = p [ ' headline ' ] . slice ( 0 , shortDescriptionMaxLength )
if p [ ' summary ' ]
2014-06-08 20:38:02 -04:00
longDescriptionMaxLength = jobProfileSchema . longDescription . maxLength
2014-06-30 22:16:26 -04:00
currentJobProfile.longDescription = p . summary . slice ( 0 , longDescriptionMaxLength )
if p [ ' positions ' ] ? [ ' values ' ] ? . length
2014-06-07 14:45:49 -04:00
newWorks = [ ]
2014-06-08 20:38:02 -04:00
workSchema = jobProfileSchema . work . items . properties
2014-06-30 22:16:26 -04:00
for position in p [ ' positions ' ] [ ' values ' ]
2014-06-07 14:45:49 -04:00
workObj = { }
2014-06-08 20:38:02 -04:00
descriptionMaxLength = workSchema . description . maxLength
2014-06-10 19:30:07 -04:00
2014-06-30 22:16:26 -04:00
workObj.description = position . summary ? . slice ( 0 , descriptionMaxLength )
workObj . description ? = ' '
2014-06-08 20:38:02 -04:00
if position . startDate ? . year ?
2014-06-07 14:45:49 -04:00
workObj.duration = " #{ position . startDate . year } - "
if ( not position . endDate ? . year ) or ( position . endDate ? . year and position . endDate ? . year > ( new Date ( ) . getFullYear ( ) ) )
2014-06-30 22:16:26 -04:00
workObj . duration += ' present '
2014-06-07 14:45:49 -04:00
else
workObj . duration += position . endDate . year
else
2014-06-30 22:16:26 -04:00
workObj.duration = ' '
2014-06-08 20:38:02 -04:00
durationMaxLength = workSchema . duration . maxLength
2014-06-30 22:16:26 -04:00
workObj.duration = workObj . duration . slice ( 0 , durationMaxLength )
2014-06-08 20:38:02 -04:00
employerMaxLength = workSchema . employer . maxLength
2014-06-30 22:16:26 -04:00
workObj.employer = position . company ? . name ? ' '
workObj.employer = workObj . employer . slice ( 0 , employerMaxLength )
workObj.role = position . title ? ' '
2014-06-08 20:38:02 -04:00
roleMaxLength = workSchema . role . maxLength
2014-06-30 22:16:26 -04:00
workObj.role = workObj . role . slice ( 0 , roleMaxLength )
2014-06-07 14:45:49 -04:00
newWorks . push workObj
currentJobProfile.work = newWorks
2014-06-08 21:37:33 -04:00
2014-06-30 22:16:26 -04:00
if p [ ' educations ' ] ? [ ' values ' ] ? . length
2014-06-07 14:45:49 -04:00
newEducation = [ ]
2014-06-08 20:38:02 -04:00
eduSchema = jobProfileSchema . education . items . properties
2014-06-30 22:16:26 -04:00
for education in p [ ' educations ' ] [ ' values ' ]
2014-06-07 14:45:49 -04:00
educationObject = { }
2014-06-30 22:16:26 -04:00
educationObject.degree = education . degree ? ' Studied '
2014-06-08 21:37:33 -04:00
2014-06-08 20:38:02 -04:00
if education . startDate ? . year ?
2014-06-07 14:45:49 -04:00
educationObject.duration = " #{ education . startDate . year } - "
if ( not education . endDate ? . year ) or ( education . endDate ? . year and education . endDate ? . year > ( new Date ( ) . getFullYear ( ) ) )
2014-06-30 22:16:26 -04:00
educationObject . duration += ' present '
if educationObject . degree is ' Studied '
educationObject.degree = ' Studying '
2014-06-07 14:45:49 -04:00
else
educationObject . duration += education . endDate . year
2014-06-08 20:38:02 -04:00
else
2014-06-30 22:16:26 -04:00
educationObject.duration = ' '
2014-06-08 21:15:16 -04:00
if education . fieldOfStudy
2014-06-30 22:16:26 -04:00
if educationObject . degree is ' Studied ' or educationObject . degree is ' Studying '
2014-06-08 21:15:16 -04:00
educationObject . degree += " #{ education . fieldOfStudy } "
else
educationObject . degree += " in #{ education . fieldOfStudy } "
2014-06-30 22:16:26 -04:00
educationObject.degree = educationObject . degree . slice ( 0 , eduSchema . degree . maxLength )
educationObject.duration = educationObject . duration . slice ( 0 , eduSchema . duration . maxLength )
educationObject.school = education . schoolName ? ' '
educationObject.school = educationObject . school . slice ( 0 , eduSchema . school . maxLength )
educationObject.description = ' '
2014-06-07 14:45:49 -04:00
newEducation . push educationObject
currentJobProfile.education = newEducation
2014-06-30 22:16:26 -04:00
if p [ ' publicProfileUrl ' ]
2014-06-08 21:15:16 -04:00
#search for linkedin link
links = currentJobProfile . links
alreadyHasLinkedIn = false
for link in links
2014-06-30 22:16:26 -04:00
if link . link . toLowerCase ( ) . indexOf ( ' linkedin ' ) > - 1
2014-06-08 21:15:16 -04:00
alreadyHasLinkedIn = true
break
unless alreadyHasLinkedIn
2014-06-08 21:37:33 -04:00
newLink =
2014-06-30 22:16:26 -04:00
link: p [ ' publicProfileUrl ' ]
name: ' LinkedIn '
2014-06-08 21:15:16 -04:00
currentJobProfile . links . push newLink
2014-06-30 22:16:26 -04:00
@ user . set ( ' jobProfile ' , currentJobProfile )
2014-06-08 20:38:02 -04:00
validationErrors = @ user . validate ( )
2014-06-08 21:37:33 -04:00
if validationErrors
2014-06-30 22:16:26 -04:00
@ user . set ( ' jobProfile ' , oldJobProfile )
2014-06-08 21:15:16 -04:00
return alert ( " Please notify team@codecombat.com! There were validation errors from the LinkedIn import: #{ JSON . stringify validationErrors } " )
2014-06-08 20:38:02 -04:00
else
@ render ( )
2014-06-08 21:37:33 -04:00
2014-01-03 13:32:13 -05:00
getRenderData: ->
context = super ( )
2014-06-07 19:33:14 -04:00
context.userID = @ userID
2014-06-07 14:45:49 -04:00
context.linkedInAuthorized = @ authorizedWithLinkedIn
2014-06-01 19:45:19 -04:00
context.jobProfileSchema = me . schema ( ) . properties . jobProfile
2014-06-07 19:33:14 -04:00
if @ user and not jobProfile = @ user . get ' jobProfile '
2014-06-01 19:45:19 -04:00
jobProfile = { }
for prop , schema of context . jobProfileSchema . properties
jobProfile [ prop ] = _ . clone schema . default if schema . default ?
2014-06-04 17:38:57 -04:00
for prop in context . jobProfileSchema . required
jobProfile [ prop ] ? = { string: ' ' , boolean: false , number: 0 , integer: 0 , array: [ ] } [ context . jobProfileSchema . properties [ prop ] . type ]
2014-06-01 19:45:19 -04:00
@ user . set ' jobProfile ' , jobProfile
2014-06-07 19:33:14 -04:00
jobProfile . name ? = ( @ user . get ( ' firstName ' ) + ' ' + @ user . get ( ' lastName ' ) ) . trim ( ) if @ user ? . get ( ' firstName ' )
2014-06-01 19:45:19 -04:00
context.profile = jobProfile
2014-04-09 19:46:44 -04:00
context.user = @ user
2014-06-07 19:33:14 -04:00
context.myProfile = @ user ? . id is context . me . id
2014-06-30 22:16:26 -04:00
context.allowedToViewJobProfile = @ user and ( me . isAdmin ( ) or ' employer ' in me . get ( ' permissions ' ) or ( context . myProfile && ! me . get ( ' anonymous ' ) ) )
2014-06-07 19:33:14 -04:00
context.allowedToEditJobProfile = @ user and ( me . isAdmin ( ) or ( context . myProfile && ! me . get ( ' anonymous ' ) ) )
context.profileApproved = @ user ? . get ' jobProfileApproved '
2014-06-01 01:09:41 -04:00
context.progress = @ progress ? @ updateProgress ( )
2014-06-08 13:52:16 -04:00
@ editing ? = context . myProfile and context . progress < 0.8
2014-05-31 01:12:44 -04:00
context.editing = @ editing
2014-04-07 18:21:05 -04:00
context.marked = marked
context.moment = moment
2014-04-09 16:14:52 -04:00
context.iconForLink = @ iconForLink
2014-06-07 19:33:14 -04:00
if links = jobProfile ? . links
2014-04-09 16:14:52 -04:00
links = ( $ . extend ( true , { } , link ) for link in links )
link.icon = @ iconForLink link for link in links
context.profileLinks = _ . sortBy links , (link) -> not link . icon # icons first
2014-06-12 17:44:55 -04:00
if @ sessions
2014-06-18 15:05:40 -04:00
context.sessions = ( s . attributes for s in @ sessions . models when ( s . get ( ' submitted ' ) or ( s . get ( ' levelID ' ) is ' gridmancer ' ) and s . get ( ' code ' ) ? . thoktar ? . plan ? . length isnt 942 ) ) # no default code
2014-06-12 17:44:55 -04:00
context . sessions . sort (a, b) -> ( b . playtime ? 0 ) - ( a . playtime ? 0 )
else
context.sessions = [ ]
2014-06-17 16:03:08 -04:00
context.adminContacts = adminContacts
context.remark = @ remark
2014-01-03 13:32:13 -05:00
context
2014-04-07 20:58:02 -04:00
afterRender: ->
super ( )
2014-06-14 23:59:28 -04:00
if me . get ( ' employerAt ' )
@ $el . addClass ' viewed-by-employer '
2014-06-07 19:33:14 -04:00
return unless @ user
2014-05-31 01:12:44 -04:00
unless @ user . get ( ' jobProfile ' ) ? . projects ? . length or @ editing
2014-04-10 20:54:28 -04:00
@ $el . find ( ' .right-column ' ) . hide ( )
@ $el . find ( ' .middle-column ' ) . addClass ( ' double-column ' )
2014-05-31 01:12:44 -04:00
unless @ editing
@ $el . find ( ' .editable-display ' ) . attr ( ' title ' , ' ' )
2014-06-01 19:45:19 -04:00
@ initializeAutocomplete ( )
2014-06-07 02:31:19 -04:00
highlightNext = @ highlightNext ? true
2014-06-30 22:16:26 -04:00
justSavedSection = @ $el . find ( ' # ' + @ justSavedSectionID ) . addClass ' just-saved '
2014-06-01 19:45:19 -04:00
_ . defer =>
@progress = @ updateProgress highlightNext
_ . delay ->
2014-06-30 22:16:26 -04:00
justSavedSection . removeClass ' just-saved ' , duration: 1500 , easing: ' easeOutQuad '
2014-06-01 19:45:19 -04:00
, 500
2014-06-17 16:03:08 -04:00
if me . isAdmin ( )
visibleSettings = [ ' history ' , ' tasks ' ]
data = _ . pick ( @ remark . attributes ) , (value, key) -> key in visibleSettings
data . history ? = [ ]
data . tasks ? = [ ]
schema = _ . cloneDeep @ remark . schema ( )
schema.properties = _ . pick schema . properties , (value, key) => key in visibleSettings
schema.required = _ . intersection ( schema . required ? [ ] ) , visibleSettings
treemaOptions =
filePath: " db/user/ #{ @ userID } "
schema: schema
data: data
aceUseWrapMode: true
callbacks: { change: @ onRemarkChanged }
@remarkTreema = @ $el . find ( ' # remark-treema ' ) . treema treemaOptions
@ remarkTreema . build ( )
@ remarkTreema . open ( 3 )
onRemarkChanged: (e) =>
return unless @ remarkTreema . isValid ( )
for key in [ ' history ' , ' tasks ' ]
val = _ . filter ( @ remarkTreema . get ( key ) , (entry) -> entry ? . content or entry ? . action )
entry . date ? = ( new Date ( ) ) . toISOString ( ) for entry in val if key is ' history '
@ remark . set key , val
@ saveRemark ( )
2014-06-01 19:45:19 -04:00
initializeAutocomplete: (container) ->
( container ? @ $el ) . find ( ' input[data-autocomplete] ' ) . each ->
2014-07-23 10:02:45 -04:00
$ ( @ ) . autocomplete ( source: JobProfileTreemaView [ $ ( @ ) . data ( ' autocomplete ' ) ] , minLength: parseInt ( $ ( @ ) . data ( ' autocomplete-min-length ' ) ) , delay: 0 , autoFocus: true )
2014-04-07 20:58:02 -04:00
2014-05-31 01:12:44 -04:00
toggleEditing: ->
@editing = not @ editing
@ render ( )
2014-06-08 21:37:33 -04:00
_ . delay @ renderLinkedInButton , 1000
2014-06-08 21:21:56 -04:00
@ saveEdits ( )
2014-05-31 01:12:44 -04:00
2014-04-07 20:58:02 -04:00
toggleJobProfileApproved: ->
2014-06-04 17:38:57 -04:00
return unless me . isAdmin ( )
2014-04-07 20:58:02 -04:00
approved = not @ user . get ' jobProfileApproved '
@ user . set ' jobProfileApproved ' , approved
2014-07-16 13:51:44 -04:00
res = @ user . patch ( )
2014-06-01 14:45:01 -04:00
res . success (model, response, options) => @ render ( )
2014-04-07 20:58:02 -04:00
2014-06-01 01:09:41 -04:00
toggleJobProfileActive: ->
active = not @ user . get ( ' jobProfile ' ) . active
@ user . get ( ' jobProfile ' ) . active = active
@ saveEdits ( )
2014-06-14 23:59:28 -04:00
if active and not ( me . isAdmin ( ) or @ stackLed )
2014-06-30 22:16:26 -04:00
$ . post ' /stacklead '
2014-06-14 23:59:28 -04:00
@stackLed = true
2014-06-01 01:09:41 -04:00
2014-04-25 19:57:42 -04:00
enterEspionageMode: ->
postData = emailLower: @ user . get ( ' email ' ) . toLowerCase ( ) , usernameLower: @ user . get ( ' name ' ) . toLowerCase ( )
$ . ajax
2014-06-30 22:16:26 -04:00
type: ' POST ' ,
url: ' /auth/spy '
2014-04-25 19:57:42 -04:00
data: postData
success: @ espionageSuccess
espionageSuccess: (model) ->
window . location . reload ( )
2014-06-17 18:17:19 -04:00
openModelModal: (e) ->
@ openModalView new ModelModal models: [ @ user ]
2014-04-07 20:58:02 -04:00
onJobProfileNotesChanged: (e) =>
2014-06-30 22:16:26 -04:00
notes = @ $el . find ( ' # job-profile-notes ' ) . val ( )
2014-04-07 20:58:02 -04:00
@ user . set ' jobProfileNotes ' , notes
2014-06-01 14:45:01 -04:00
@ user . save { jobProfileNotes: notes } , { patch: true }
2014-04-09 16:14:52 -04:00
iconForLink: (link) ->
icons = [
2014-04-23 14:26:20 -04:00
{ icon: ' facebook ' , name: ' Facebook ' , domain: /facebook\.com/ , match: /facebook/i }
{ icon: ' twitter ' , name: ' Twitter ' , domain: /twitter\.com/ , match: /twitter/i }
{ icon: ' github ' , name: ' GitHub ' , domain: /github\.(com|io)/ , match: /github/i }
{ icon: ' gplus ' , name: ' Google Plus ' , domain: /plus\.google\.com/ , match: /(google|^g).?(\+|plus)/i }
{ icon: ' linkedin ' , name: ' LinkedIn ' , domain: /linkedin\.com/ , match: /(google|^g).?(\+|plus)/i }
2014-04-09 16:14:52 -04:00
]
for icon in icons
if ( link . name . search ( icon . match ) isnt - 1 ) or ( link . link . search ( icon . domain ) isnt - 1 )
icon.url = " /images/pages/account/profile/icon_ #{ icon . icon } .png "
return icon
null
2014-04-10 17:59:32 -04:00
onContactCandidate: (e) ->
2014-07-23 10:02:45 -04:00
@ openModalView new JobProfileContactModal recipientID: @ user . id , recipientUserName: @ user . get ( ' name ' )
2014-05-31 01:12:44 -04:00
2014-06-01 19:45:19 -04:00
showErrors: (errors) ->
section = @ $el . find ' .saving '
2014-06-30 22:16:26 -04:00
console . error ' Couldn \' t save because of validation errors: ' , errors
2014-06-01 19:45:19 -04:00
section . removeClass ' saving '
forms . clearFormAlerts section
# This is pretty lame, since we don't easily match which field had the error like forms.applyErrorsToForm can.
section . find ( ' form ' ) . addClass ( ' has-error ' ) . find ( ' .save-section ' ) . before ( $ ( " <span class= ' help-block error-help-block ' > #{ errors [ 0 ] . message } </span> " ) )
saveEdits: (highlightNext) ->
errors = @ user . validate ( )
return @ showErrors errors if errors
2014-05-31 01:12:44 -04:00
jobProfile = @ user . get ( ' jobProfile ' )
2014-06-11 22:38:41 -04:00
jobProfile.updated = ( new Date ( ) ) . toISOString ( ) if @ user is me
2014-05-31 01:12:44 -04:00
@ user . set ' jobProfile ' , jobProfile
return unless res = @ user . save ( )
2014-06-01 19:45:19 -04:00
res . error =>
return if @ destroyed
@ showErrors [ message: res . responseText ]
2014-05-31 01:12:44 -04:00
res . success (model, response, options) =>
2014-06-01 19:45:19 -04:00
return if @ destroyed
@justSavedSectionID = @ $el . find ( ' .editable-section.saving ' ) . removeClass ( ' saving ' ) . attr ( ' id ' )
@highlightNext = highlightNext
2014-05-31 01:12:44 -04:00
@ render ( )
2014-06-01 19:45:19 -04:00
@highlightNext = false
@justSavedSectionID = null
2014-05-31 01:12:44 -04:00
onEditProfilePhoto: (e) ->
2014-06-01 01:09:41 -04:00
onSaving = =>
@ $el . find ( ' .profile-photo ' ) . addClass ( ' saving ' )
onSaved = (uploadingPath) =>
@ user . get ( ' jobProfile ' ) . photoURL = uploadingPath
@ saveEdits ( )
filepicker . pick { mimetypes: ' image/* ' } , @ onImageChosen ( onSaving , onSaved )
onEditProjectImage: (e) ->
img = $ ( e . target )
onSaving = =>
img . addClass ( ' saving ' )
onSaved = (uploadingPath) =>
img . parent ( ) . find ( ' input ' ) . val ( uploadingPath )
img . css ( ' background-image ' , " url( ' /file/ #{ uploadingPath } ' ) " )
img . removeClass ( ' saving ' )
filepicker . pick { mimetypes: ' image/* ' } , @ onImageChosen ( onSaving , onSaved )
formatImagePostData: (inkBlob) ->
url: inkBlob . url , filename: inkBlob . filename , mimetype: inkBlob . mimetype , path: @ uploadFilePath , force: true
onImageChosen: (onSaving, onSaved) ->
(inkBlob) =>
onSaving ( )
uploadingPath = [ @ uploadFilePath , inkBlob . filename ] . join ( ' / ' )
$ . ajax ' /file ' , type: ' POST ' , data: @ formatImagePostData ( inkBlob ) , success: @ onImageUploaded ( onSaved , uploadingPath )
onImageUploaded: (onSaved, uploadingPath) ->
(e) =>
onSaved uploadingPath
2014-05-31 01:12:44 -04:00
onEditSection: (e) ->
2014-06-07 02:31:19 -04:00
@ $el . find ( ' .emphasized ' ) . removeClass ( ' emphasized ' )
2014-06-01 19:45:19 -04:00
section = $ ( e . target ) . closest ( ' .editable-section ' ) . removeClass ' deemphasized '
section . find ( ' .editable-form ' ) . show ( ) . find ( ' select, input, textarea ' ) . first ( ) . focus ( )
2014-05-31 01:12:44 -04:00
section . find ( ' .editable-display ' ) . hide ( )
@ $el . find ( ' .editable-section ' ) . not ( section ) . addClass ' deemphasized '
column = section . closest ( ' .full-height-column ' )
@ $el . find ( ' .full-height-column ' ) . not ( column ) . addClass ' deemphasized '
onCancelSectionEdit: (e) ->
@ render ( )
onSaveSection: (e) ->
e . preventDefault ( )
section = $ ( e . target ) . closest ( ' .editable-section ' )
2014-06-01 01:09:41 -04:00
form = $ ( e . target ) . closest ( ' form ' )
2014-05-31 01:12:44 -04:00
isEmpty = @ arrayItemIsEmpty
2014-06-01 01:09:41 -04:00
section . find ( ' .array-item ' ) . each ->
2014-05-31 01:12:44 -04:00
$ ( @ ) . remove ( ) if isEmpty @
resetOnce = false # We have to clear out arrays if we're going to redo them
2014-06-01 01:09:41 -04:00
serialized = form . serializeArray ( )
jobProfile = @ user . get ' jobProfile '
rootPropertiesSeen = { }
for field in serialized
2014-05-31 01:12:44 -04:00
keyChain = @ extractFieldKeyChain field . name
value = @ extractFieldValue keyChain [ 0 ] , field . value
2014-06-01 01:09:41 -04:00
parent = jobProfile
2014-05-31 01:12:44 -04:00
for key , i in keyChain
2014-06-01 01:09:41 -04:00
rootPropertiesSeen [ key ] = true unless i
2014-05-31 01:12:44 -04:00
break if i is keyChain . length - 1
child = parent [ key ]
if _ . isArray ( child ) and not resetOnce
child = parent [ key ] = [ ]
resetOnce = true
else unless child ?
child = parent [ key ] = { }
parent = child
2014-06-01 19:45:19 -04:00
if key is ' link ' and keyChain [ 0 ] is ' projects ' and not value
delete parent [ key ]
else
parent [ key ] = value
2014-06-01 01:09:41 -04:00
form . find ( ' .editable-array ' ) . each ->
key = $ ( @ ) . data ( ' property ' )
unless rootPropertiesSeen [ key ]
jobProfile [ key ] = [ ]
if section . hasClass ( ' projects-container ' ) and not section . find ( ' .array-item ' ) . length
jobProfile.projects = [ ]
2014-05-31 01:12:44 -04:00
section . addClass ' saving '
2014-06-01 19:45:19 -04:00
@ saveEdits true
2014-05-31 01:12:44 -04:00
extractFieldKeyChain: (key) ->
2014-06-30 22:16:26 -04:00
# 'root[projects][0][name]' -> ['projects', '0', 'name']
2014-05-31 01:12:44 -04:00
key . replace ( /^root/ , ' ' ) . replace ( /\[(.*?)\]/g , ' .$1 ' ) . replace ( /^\./ , ' ' ) . split ( /\./ )
extractFieldValue: (key, value) ->
switch key
when ' active ' then Boolean value
2014-06-01 01:09:41 -04:00
when ' experience ' then parseInt value or ' 0 '
2014-05-31 01:12:44 -04:00
else value
arrayItemIsEmpty: (arrayItem) ->
2014-06-01 01:09:41 -04:00
for input in $ ( arrayItem ) . find ( ' input[type!=hidden], textarea ' )
return false if $ ( input ) . val ( ) . trim ( )
2014-05-31 01:12:44 -04:00
true
onEditArray: (e) ->
2014-06-01 01:09:41 -04:00
# We make sure there's always an empty array item at the end for the user to add to, deleting interstitial empties.
2014-05-31 01:12:44 -04:00
array = $ ( e . target ) . closest ( ' .editable-array ' )
arrayItems = array . find ( ' .array-item ' )
toRemove = [ ]
for arrayItem , index in arrayItems
empty = @ arrayItemIsEmpty arrayItem
if index is arrayItems . length - 1
lastEmpty = empty
2014-06-01 01:09:41 -04:00
else if empty and not $ ( arrayItem ) . find ( ' input:focus, textarea:focus ' ) . length
2014-05-31 01:12:44 -04:00
toRemove . unshift index
$ ( arrayItems [ emptyIndex ] ) . remove ( ) for emptyIndex in toRemove
unless lastEmpty
2014-06-01 19:45:19 -04:00
clone = $ ( arrayItem ) . clone ( false )
2014-05-31 01:12:44 -04:00
clone . find ( ' input ' ) . each -> $ ( @ ) . val ( ' ' )
2014-06-01 01:09:41 -04:00
clone . find ( ' textarea ' ) . each -> $ ( @ ) . text ( ' ' )
2014-05-31 01:12:44 -04:00
array . append clone
2014-06-01 19:45:19 -04:00
@ initializeAutocomplete clone
2014-05-31 01:12:44 -04:00
for arrayItem , index in array . find ( ' .array-item ' )
2014-06-01 01:09:41 -04:00
for input in $ ( arrayItem ) . find ( ' input, textarea ' )
2014-05-31 01:12:44 -04:00
$ ( input ) . attr ( ' name ' , $ ( input ) . attr ( ' name ' ) . replace ( /\[\d+\]/ , " [ #{ index } ] " ) )
2014-06-01 01:09:41 -04:00
onClickLinkWhileEditing: (e) ->
e . preventDefault ( )
2014-06-17 16:03:08 -04:00
onAdminContactChanged: (e) ->
newContact = @ $el . find ( ' # admin-contact ' ) . val ( )
newContactName = if newContact then _ . find ( adminContacts , id: newContact ) . name else ' '
@ remark . set ' contact ' , newContact
@ remark . set ' contactName ' , newContactName
@ saveRemark ( )
saveRemark: ->
@ remark . set ' user ' , @ user . id
@ remark . set ' userName ' , @ user . get ( ' name ' )
if errors = @ remark . validate ( )
2014-06-30 22:16:26 -04:00
return console . error ' UserRemark ' , @ remark , ' failed validation with errors: ' , errors
2014-06-17 16:03:08 -04:00
res = @ remark . save ( )
res . error =>
return if @ destroyed
2014-06-30 22:16:26 -04:00
console . error ' UserRemark ' , @ remark , ' failed to save with error: ' , res . responseText
2014-06-17 16:03:08 -04:00
res . success (model, response, options) =>
return if @ destroyed
2014-06-30 22:16:26 -04:00
console . log ' Saved UserRemark ' , @ remark , ' with response ' , response
2014-06-17 16:03:08 -04:00
2014-06-01 19:45:19 -04:00
updateProgress: (highlightNext) ->
2014-06-07 19:33:14 -04:00
return unless @ user
2014-06-01 01:09:41 -04:00
completed = 0
totalWeight = 0
next = null
for metric in metrics = @ getProgressMetrics ( )
done = metric . fn ( )
completed += metric . weight if done
totalWeight += metric . weight
2014-06-01 19:45:19 -04:00
next = metric unless next or done
2014-06-01 01:09:41 -04:00
progress = Math . round 100 * completed / totalWeight
bar = @ $el . find ( ' .profile-completion-progress .progress-bar ' )
bar . css ' width ' , " #{ progress } % "
2014-06-07 02:31:19 -04:00
if next
2014-06-30 22:16:26 -04:00
text = ' '
2014-06-07 02:31:19 -04:00
t = $ . i18n . t
2014-06-01 19:45:19 -04:00
text = " #{ progress } % #{ t ' account_profile.complete ' } . #{ t ' account_profile.next ' } : #{ next . name } "
2014-06-07 02:31:19 -04:00
bar . parent ( ) . show ( ) . find ( ' .progress-text ' ) . text text
if highlightNext and next ? . container and not ( next . container in @ highlightedContainers )
@ highlightedContainers . push next . container
@ $el . find ( next . container ) . addClass ' emphasized '
#@onEditSection target: next.container
#$('#page-container').scrollTop 0
else
bar . parent ( ) . hide ( )
2014-06-01 01:09:41 -04:00
completed / totalWeight
getProgressMetrics: ->
schema = me . schema ( ) . properties . jobProfile
2014-06-01 14:45:01 -04:00
jobProfile = @ user . get ( ' jobProfile ' ) ? { }
2014-06-01 01:09:41 -04:00
exists = (field) -> -> jobProfile [ field ]
modified = (field) -> -> jobProfile [ field ] and jobProfile [ field ] isnt schema . properties [ field ] . default
listStarted = (field, subfields) -> -> jobProfile [ field ] ? . length and _ . every subfields , (subfield) -> jobProfile [ field ] [ 0 ] [ subfield ]
2014-06-01 19:45:19 -04:00
t = $ . i18n . t
2014-06-01 01:09:41 -04:00
@progressMetrics = [
2014-06-01 19:45:19 -04:00
{ name: t ( ' account_profile.next_name ' ) , weight: 1 , container: ' # name-container ' , fn: modified ' name ' }
{ name: t ( ' account_profile.next_short_description ' ) , weight: 2 , container: ' # short-description-container ' , fn: modified ' shortDescription ' }
{ name: t ( ' account_profile.next_skills ' ) , weight: 2 , container: ' # skills-container ' , fn: -> jobProfile . skills ? . length >= 5 }
{ name: t ( ' account_profile.next_long_description ' ) , weight: 3 , container: ' # long-description-container ' , fn: modified ' longDescription ' }
{ name: t ( ' account_profile.next_work ' ) , weight: 3 , container: ' # work-container ' , fn: listStarted ' work ' , [ ' role ' , ' employer ' ] }
{ name: t ( ' account_profile.next_education ' ) , weight: 3 , container: ' # education-container ' , fn: listStarted ' education ' , [ ' degree ' , ' school ' ] }
{ name: t ( ' account_profile.next_projects ' ) , weight: 3 , container: ' # projects-container ' , fn: listStarted ' projects ' , [ ' name ' ] }
2014-06-07 02:31:19 -04:00
{ name: t ( ' account_profile.next_city ' ) , weight: 1 , container: ' # basic-info-container ' , fn: modified ' city ' }
{ name: t ( ' account_profile.next_country ' ) , weight: 0 , container: ' # basic-info-container ' , fn: exists ' country ' }
2014-06-01 19:45:19 -04:00
{ name: t ( ' account_profile.next_links ' ) , weight: 2 , container: ' # links-container ' , fn: listStarted ' links ' , [ ' link ' , ' name ' ] }
{ name: t ( ' account_profile.next_photo ' ) , weight: 2 , container: ' # profile-photo-container ' , fn: modified ' photoURL ' }
{ name: t ( ' account_profile.next_active ' ) , weight: 1 , fn: modified ' active ' }
2014-06-01 01:09:41 -04:00
]
2014-07-17 12:12:21 -04:00
onSessionLinkPressed: (e) ->
sessionID = $ ( e . target ) . closest ( ' .session-link ' ) . data ( ' session-id ' )
session = _ . find @ sessions . models , (session) -> session . id is sessionID
modal = new JobProfileCodeModal ( { session : session } )
@ openModalView modal