Started the horrifying migration to an inline jobs profile editor.

This commit is contained in:
Nick Winter 2014-05-30 22:12:44 -07:00
parent 29011fab2a
commit 852053022a
7 changed files with 374 additions and 93 deletions

View file

@ -186,6 +186,7 @@
account_profile:
edit_settings: "Edit Settings"
done_editing_settings: "Done Editing"
profile_for_prefix: "Profile for "
profile_for_suffix: ""
approved: "Approved"

View file

@ -16,6 +16,13 @@ module.exports = class ThangType extends CocoModel
@on 'sync', @setDefaults
@spriteSheets = {}
## Testing memory clearing
#f = =>
# console.info 'resetting raw data'
# @unset 'raw'
# @_previousAttributes.raw = null
#setTimeout f, 40000
setDefaults: ->
@resetRawData() unless @get('raw')

View file

@ -1,4 +1,8 @@
@import "app/styles/bootstrap/variables"
#profile-view
$sideBackground: rgb(220, 220, 220)
.profile-control-bar
background-color: rgb(78, 78, 78)
width: 100%
@ -14,6 +18,7 @@
.main-content-area
padding: 0
background-color: white
.flat-button
width: 100%
@ -68,7 +73,7 @@
.left-column
width: $side-width - 2 * $side-padding
padding: $side-padding
background-color: rgb(220, 220, 220)
background-color: $sideBackground
.sub-column
width: $side-width - 2 * $side-padding
@ -81,7 +86,7 @@
img.profile-photo
width: $side-width - 2 * $side-padding
border-radius: 6px
.profile-caption
background-color: rgba(0, 0, 0, 0.5)
color: white
@ -128,7 +133,7 @@
overflow-wrap: break-word
code
background-color: rgb(220, 220, 220)
background-color: $sideBackground
color: #555
margin: 2px 0
display: inline-block
@ -161,7 +166,7 @@
.right-column
width: $side-width
background-color: rgb(220, 220, 220)
background-color: $sideBackground
.sub-column
width: $side-width - 2 * $side-padding
@ -176,7 +181,7 @@
li
margin-bottom: 10px
padding: 5px 3px
border: 2px solid rgb(220, 220, 220)
border: 2px solid $sideBackground
transition: .5s ease-in-out
position: relative
background-color: white
@ -218,3 +223,54 @@
-moz-filter: grayscale(0%)
-o-filter: grayscale(0%)
filter: grayscale(0%)
.main-content-area
.job-profile-container
.editable-section
position: relative
.editable-form
display: none
background-color: white
padding: 5px 5px 5px 5px
.editable-icon
display: none
.job-profile-container.editable-profile
.full-height-column.deemphasized
background-color: $sideBackground
.saving
opacity: 0.75
.editable-thinner
padding-right: 30px
.editable-icon
display: block
position: absolute
right: 5px
top: 5px
font-size: 20px
color: $blue
opacity: 0.5
.edit-label
color: $blue
.editable-section.deemphasized, .our-notes-section.deemphasized
opacity: 0.5
.editable-section:hover
cursor: pointer
outline: 1px solid $blue
.editable-icon
opacity: 1.0
cursor: pointer
.editable-form
cursor: default

View file

@ -1,6 +1,5 @@
@import "bootstrap/variables"
@import "bootstrap/mixins"
@import "bootstrap/variables"
html
background-color: #2f261d

View file

@ -2,120 +2,214 @@ extends /templates/base
block content
if myProfile || (me.isAdmin() && user.get('jobProfile'))
if allowedToEditJobProfile
.profile-control-bar
if myProfile
a(href=user.get('jobProfile') ? "/account/settings#job-profile" : "/account/settings")
button.btn.edit-settings-button
i.icon-cog
span(data-i18n="account_profile.edit_settings") Edit Settings
button.btn.edit-settings-button#toggle-editing
i.icon-cog
if editing
span(data-i18n="account_profile.done_editing_settings") Done Editing
else
span(data-i18n="account_profile.edit_settings") Edit Settings
if me.isAdmin() && user.get('jobProfile')
button.btn.edit-settings-button#toggle-job-profile-approved
i.icon-cog
span(data-i18n='account_profile.approved').approved Approved
span(data-i18n='account_profile.not_approved').not-approved Not Approved
if user.id != me.id
if !myProfile
button.btn.edit-settings-button#enter-espionage-mode 007
if user.get('jobProfile') && allowedToViewJobProfile
- var profile = user.get('jobProfile');
.job-profile-container
div(class="job-profile-container" + (editing ? " editable-profile" : ""))
.job-profile-row
.left-column.full-height-column
.sub-column
.profile-photo-container
.profile-photo-container.editable-section(title="Click to change your photo")
.editable-icon.glyphicon.glyphicon-pencil
img.profile-photo(src=user.getPhotoURL(240, true))
.profile-caption= profile.jobTitle || 'Software Developer'
if profileLinks.length
ul.links
each link in profileLinks
if link.link && link.name
li(title=profile.name + " on " + link.name, class=link.icon ? "has-icon" : "")
a(href=link.link)
if link.icon
img(src=link.icon.url, alt=link.icon.name)
else
button.btn.btn-large.btn-inverse.flat-button= link.name
.links-container.editable-section(title="Click to add social and personal links")
.editable-display(title="Click to edit your basic info")
.editable-icon.glyphicon.glyphicon-pencil
if profileLinks.length
ul.links.editable-thinner
each link in profileLinks
if link.link && link.name
li(title=profile.name + " on " + link.name, class=link.icon ? "has-icon" : "")
a(href=link.link)
if link.icon
img(src=link.icon.url, alt=link.icon.name)
else
button.btn.btn-large.btn-inverse.flat-button= link.name
else if editing
h3.edit-label(data-i18n="account_profile.add_links") Add some links
form.editable-form
.editable-icon.glyphicon.glyphicon-remove
h3 Personal Links
p.help-block Link any other sites or profiles you want to highlight, like your GitHub, your LinkedIn, or your blog.
.editable-array
for link, index in profile.links.concat({})
.array-item.link-container.well.well-sm
.form-group
label.control-label Link Name
input.form-control(type='link-name', maxlength='30', data-schemaformat='link-name', name='root[links][' + index + '][name]', value=link.name)
if !index
p.help-block What are you linking to? Ex: "Personal Website", "GitHub"
.form-group
label.control-label Link URL
input.form-control(type='url', pattern='^(ht|f)tp(s?)://[0-9a-zA-Z]([-.w]*[0-9a-zA-Z])*(:(0-9)*)*(/?)([a-zA-Z0-9-.?,\'/\+&%$#_=]*)?$', data-schemaformat='url', name='root[links][' + index + '][link]', value=link.link)
if !index
p.help-block Ex.: "https://github.com/nwinter"
button.btn.btn-success.btn-block.save-section(data-i18n="common.save") Save
.basic-info-container.editable-section
.editable-display(title="Click to edit your basic info")
.editable-icon.glyphicon.glyphicon-pencil
if editing && profile.city == "Defaultsville, CA"
h3.edit-label Update basic info
div= profile.city + ', ' + profile.country
div= profile.visa
div
span(data-i18n="account_profile.looking_for") Looking for:
| #{profile.lookingFor}
div
span(data-i18n="account_profile.last_updated") Last updated:
| #{moment(profile.updated).fromNow()}
form.editable-form
.editable-icon.glyphicon.glyphicon-remove
.form-group
label.control-label Open to Offers
select.form-control(name='root[active]')
option(value='1', selected=profile.active) Yes, I want interviews
option(value='', selected=!profile.active) No, not right now
p.help-block Want interview offers right now?
.form-group
label.control-label Desired Job Title
input.form-control(type='text', maxlength='50', name='root[jobTitle]', value=profile.jobTitle)
p.help-block
| What role are you looking for? Ex.: "Full Stack Engineer", "Front-End Developer", "iOS Developer"
.form-group
label.control-label City
input.form-control(type='city', maxlength='100', data-schemaformat='city', name='root[city]', value=profile.city)
p.help-block
| City you want to work in (or live in now), like "San Francisco" or "Lubbock, TX".
.form-group
label.control-label Country
input.form-control(type='country', maxlength='100', data-schemaformat='country', name='root[country]', value=profile.country)
p.help-block Country you want to work in (or live in now), like "USA" or "France".
.form-group
label.control-label Looking For
select.form-control(name='root[lookingFor]')
option(value='Full-time', selected=profile.lookingFor == "Full-time") Full-time
option(value='Part-time', selected=profile.lookingFor == "Part-time") Part-time
option(value='Remote', selected=profile.lookingFor == "Remote") Remote
option(value='Contracting', selected=profile.lookingFor == "Contracting") Contracting
option(value='Internship', selected=profile.lookingFor == "Internship") Internship
p.help-block What kind of developer position do you want?
button.btn.btn-success.btn-block.save-section(data-i18n="common.save") Save
div= profile.city + ', ' + profile.country
div= profile.visa
div
span(data-i18n="account_profile.looking_for") Looking for:
| #{profile.lookingFor}
div
span(data-i18n="account_profile.last_updated") Last updated:
| #{moment(profile.updated).fromNow()}
button#contact-candidate.btn.btn-large.btn-inverse.flat-button
button#contact-candidate.btn.btn-large.btn-inverse.flat-button(disabled=editing)
span(data-i18n="account_profile.contact") Contact
| #{profile.name.split(' ')[0]}
.middle-column.full-height-column
.sub-column
h3= profile.name || "Anonymous Developer"
if profile.shortDescription
p= profile.shortDescription
.name-container.editable-section(title="Click to fill in your name")
.editable-icon.glyphicon.glyphicon-pencil
h3= profile.name || (editing ? "Fill in your name" : "Anonymous Developer")
.short-description-container.editable-section(title="Click to write your short description")
.editable-icon.glyphicon.glyphicon-pencil
if editing && (!profile.shortDescription || profile.shortDescription == jobProfileSchema.properties.shortDescription.default)
h3.edit-label(data-i18n="account_profile.add_short_description") Write a short description of yourself
else if profile.shortDescription
p.editable-thinner= profile.shortDescription
.skills-container.editable-section.editable-thinner(title="Click to tag your programming skills")
.editable-icon.glyphicon.glyphicon-pencil
if editing && profile.skills.length == 1 && profile.skills[0] == 'javascript'
h3.edit-label Tag your programming skills
else
each skill in profile.skills
code= skill
span
.long-description-container.editable-section(title="Click to start writing your longer description")
.editable-icon.glyphicon.glyphicon-pencil
if editing && (!profile.longDescription || profile.longDescription == jobProfileSchema.properties.longDescription.default)
h3.edit-label Detail your desired position
else if profile.longDescription
div.long-description.editable-thinner!= marked(profile.longDescription)
each skill in profile.skills
code= skill
span
if profile.longDescription
div.long-description!= marked(profile.longDescription)
.work-container.editable-section(title="Click to add work experience")
.editable-icon.glyphicon.glyphicon-pencil
if profile.work.length
h3.experience-header
img.header-icon(src="/images/pages/account/profile/work.png", alt="")
span(data-i18n="account_profile.work_experience") Work Experience
each job in profile.work
if job.role && job.employer
div.experience-entry
div.duration.pull-right= job.duration
| #{job.role} at #{job.employer}
.clearfix
if job.description
div!= marked(job.description)
else if editing
h3.edit-label Chronicle your work history
if profile.work.length
h3.experience-header
img.header-icon(src="/images/pages/account/profile/work.png", alt="")
span(data-i18n="account_profile.work_experience") Work Experience
each job in profile.work
if job.role && job.employer
div.experience-entry
div.duration.pull-right= job.duration
| #{job.role} at #{job.employer}
.clearfix
if job.description
div!= marked(job.description)
if profile.education.length
h3.experience-header
img.header-icon(src="/images/pages/account/profile/education.png", alt="")
span(data-i18n="account_profile.education") Education
each school in profile.education
if school.degree && school.school
div.experience-entry
div.duration.pull-right= school.duration
| #{school.degree} at #{school.school}
.clearfix
if school.description
div!= marked(school.description)
.education-container.editable-section(title="Click to add academic experience")
.editable-icon.glyphicon.glyphicon-pencil
if profile.education.length
h3.experience-header
img.header-icon(src="/images/pages/account/profile/education.png", alt="")
span(data-i18n="account_profile.education") Education
each school in profile.education
if school.degree && school.school
div.experience-entry
div.duration.pull-right= school.duration
| #{school.degree} at #{school.school}
.clearfix
if school.description
div!= marked(school.description)
else if editing
h3.edit-label Recount your academic ordeals
if user.get('jobProfileNotes') || me.isAdmin()
h3.experience-header(data-i18n="account_profile.our_notes") Our Notes
- var notes = user.get('jobProfileNotes') || '';
if me.isAdmin()
textarea#job-profile-notes!= notes
button.btn.btn-primary#save-notes-button Save Notes
else
div!= marked(notes)
div(class="our-notes-section" + (editing ? " deemphasized" : ""))
h3.experience-header(data-i18n="account_profile.our_notes") Our Notes
- var notes = user.get('jobProfileNotes') || '';
if me.isAdmin()
textarea#job-profile-notes!= notes
button.btn.btn-primary#save-notes-button Save Notes
else
div!= marked(notes)
.right-column.full-height-column
.sub-column
if profile.projects.length
h3(data-i18n="account_profile.projects") Projects
ul.projects
each project in profile.projects
if project.name
li
if project.link && project.link.length && project.link != 'http://example.com'
a(href=project.link)
if project.picture
.project-image(style="background-image: url('/file/" + project.picture + "')")
p= project.name
div!= marked(project.description)
.projects-container.editable-section(title="Click to add your projects")
.editable-icon.glyphicon.glyphicon-pencil
if profile.projects.length
h3(data-i18n="account_profile.projects") Projects
ul.projects
each project in profile.projects
if project.name
li
if project.link && project.link.length && project.link != 'http://example.com'
a(href=project.link)
if project.picture
.project-image(style="background-image: url('/file/" + project.picture + "')")
p= project.name
div!= marked(project.description)
else if editing
h3.edit-label Add 3 projects
else if allowedToViewJobProfile
.public-profile-container
h2 Loading...
h2(data-i18n="common.loading") Loading...
else
.public-profile-container
@ -127,4 +221,5 @@ block content
img.profile-photo(src=user.getPhotoURL(256))
h2 TODO
p Public user profiles are not ready yet. If you are seeing this, we probably have a bug leading to a broken link.
p Public user profiles are not ready yet. If you are seeing this, we probably have a bug leading to a broken link.

View file

@ -6,12 +6,19 @@ JobProfileContactView = require 'views/modal/job_profile_contact_modal'
module.exports = class ProfileView extends View
id: "profile-view"
template: template
editing: false
events:
'click #toggle-editing': 'toggleEditing'
'click #toggle-job-profile-approved': 'toggleJobProfileApproved'
'click save-notes-button': 'onJobProfileNotesChanged'
'click #contact-candidate': 'onContactCandidate'
'click #enter-espionage-mode': 'enterEspionageMode'
'click .editable-profile .profile-photo': 'onEditProfilePhoto'
'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'
constructor: (options, @userID) ->
@onJobProfileNotesChanged = _.debounce @onJobProfileNotesChanged, 1000
@ -27,8 +34,11 @@ module.exports = class ProfileView extends View
getRenderData: ->
context = super()
context.user = @user
context.allowedToViewJobProfile = me.isAdmin() or "employer" in me.get('permissions')
context.myProfile = @user.id is context.me.id
context.allowedToViewJobProfile = me.isAdmin() or "employer" in me.get('permissions') or context.myProfile
context.allowedToEditJobProfile = me.isAdmin() or context.myProfile
context.editing = @editing
context.jobProfileSchema = me.schema().properties.jobProfile
context.marked = marked
context.moment = moment
context.iconForLink = @iconForLink
@ -41,15 +51,21 @@ module.exports = class ProfileView extends View
afterRender: ->
super()
@updateProfileApproval() if me.isAdmin()
unless @user.get('jobProfile')?.projects?.length
unless @user.get('jobProfile')?.projects?.length or @editing
@$el.find('.right-column').hide()
@$el.find('.middle-column').addClass('double-column')
unless @editing
@$el.find('.editable-display').attr('title', '')
updateProfileApproval: ->
approved = @user.get 'jobProfileApproved'
@$el.find('.approved').toggle Boolean(approved)
@$el.find('.not-approved').toggle not approved
toggleEditing: ->
@editing = not @editing
@render()
toggleJobProfileApproved: ->
approved = not @user.get 'jobProfileApproved'
@user.set 'jobProfileApproved', approved
@ -88,3 +104,109 @@ module.exports = class ProfileView extends View
onContactCandidate: (e) ->
@openModalView new JobProfileContactView recipientID: @user.id
saveEdits: (e) ->
res = @user.validate()
if res?
console.error "Couldn't save because of validation errors:", res
# TODO: show some sort of problem message here
return
jobProfile = @user.get('jobProfile')
jobProfile.updated = (new Date()).toISOString()
@user.set 'jobProfile', jobProfile
return unless res = @user.save()
res.error ->
errors = JSON.parse(res.responseText)
# TODO: show some sort of problem message here
res.success (model, response, options) =>
@render()
onEditProfilePhoto: (e) ->
filepicker.pick {mimetypes: 'image/*'}, @onProfilePhotoChosen
onProfilePhotoChosen: (inkBlob) =>
filePath = "db/user/#{@user.id}"
body =
url: inkBlob.url
filename: inkBlob.filename
mimetype: inkBlob.mimetype
path: filePath
force: true
@uploadingPath = [filePath, inkBlob.filename].join('/')
@$el.find('.profile-photo').addClass('saving')
$.ajax '/file', type: 'POST', data: body, success: @onFileUploaded
onFileUploaded: (e) =>
@user.get('jobProfile').photoURL = @uploadingPath
@saveEdits()
onEditSection: (e) ->
section = $(e.target).closest('.editable-section')
section.find('.editable-form').show()
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')
isEmpty = @arrayItemIsEmpty
section.find('.editable-array .array-item').each ->
$(@).remove() if isEmpty @
resetOnce = false # We have to clear out arrays if we're going to redo them
for field in $(e.target).closest('form').serializeArray()
keyChain = @extractFieldKeyChain field.name
value = @extractFieldValue keyChain[0], field.value
console.log "Should save", keyChain, value
parent = @user.get('jobProfile')
for key, i in keyChain
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
console.log " Setting", parent, "prop", key, "to", value
parent[key] = value
section.addClass 'saving'
@saveEdits()
extractFieldKeyChain: (key) ->
# "root[projects][0][name]" -> ["projects", "0", "name"]
key.replace(/^root/, '').replace(/\[(.*?)\]/g, '.$1').replace(/^\./, '').split(/\./)
extractFieldValue: (key, value) ->
switch key
when 'active' then Boolean value
else value
arrayItemIsEmpty: (arrayItem) ->
for input in $(arrayItem).find('input')
return false if $(input).val()
true
onEditArray: (e) ->
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
else if empty
toRemove.unshift index
$(arrayItems[emptyIndex]).remove() for emptyIndex in toRemove
unless lastEmpty
clone = $(arrayItem).clone(true)
clone.find('input').each -> $(@).val('')
array.append clone
for arrayItem, index in array.find('.array-item')
for input in $(arrayItem).find('input')
$(input).attr('name', $(input).attr('name').replace(/\[\d+\]/, "[#{index}]"))

View file

@ -107,6 +107,7 @@ module.exports = class SettingsView extends View
@grabData()
res = me.validate()
if res?
console.error "Couldn't save because of validation errors:", res
forms.applyErrorsToForm(@$el, res)
return
@ -144,7 +145,7 @@ module.exports = class SettingsView extends View
me.set 'name', $('#name', @$el).val()
me.set 'email', $('#email', @$el).val()
for emailName, enabled of @getSubscriptions()
me.setEmailSubscription emailName, enabled
me.setEmailSubscription emailName, enabled
me.set 'photoURL', @pictureTreema.get('/photoURL')
adminCheckbox = @$el.find('#admin')