mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-27 09:35:39 -05:00
Merge master into branch
This commit is contained in:
commit
9090ce7714
26 changed files with 553 additions and 285 deletions
BIN
app/assets/images/pages/base/recruitment_logo.png
Normal file
BIN
app/assets/images/pages/base/recruitment_logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3 KiB |
BIN
app/assets/images/pages/employer/anon_user.png
Normal file
BIN
app/assets/images/pages/employer/anon_user.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1 KiB |
BIN
app/assets/images/pages/employer/briefcase.png
Normal file
BIN
app/assets/images/pages/employer/briefcase.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 174 B |
BIN
app/assets/images/pages/employer/education.png
Normal file
BIN
app/assets/images/pages/employer/education.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 345 B |
BIN
app/assets/images/pages/employer/location.png
Normal file
BIN
app/assets/images/pages/employer/location.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 323 B |
BIN
app/assets/images/pages/employer/tag.png
Normal file
BIN
app/assets/images/pages/employer/tag.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 253 B |
|
@ -93,7 +93,7 @@ module.exports.makeJSONDiffer = ->
|
|||
jsondiffpatch.create({objectHash: hasher})
|
||||
|
||||
module.exports.getConflicts = (headDeltas, pendingDeltas) ->
|
||||
# headDeltas and pendingDeltas should be lists of deltas returned by interpretDelta
|
||||
# headDeltas and pendingDeltas should be lists of deltas returned by expandDelta
|
||||
# Returns a list of conflict objects with properties:
|
||||
# headDelta
|
||||
# pendingDelta
|
||||
|
@ -105,20 +105,33 @@ module.exports.getConflicts = (headDeltas, pendingDeltas) ->
|
|||
|
||||
# Here's my thinking: conflicts happen when one delta path is a substring of another delta path
|
||||
# So, sort paths from both deltas together, which will naturally make conflicts adjacent,
|
||||
# and if one is identified, one path is from the headDeltas, the other is from pendingDeltas
|
||||
# and if one is identified AND one path is from the headDeltas AND the other is from pendingDeltas
|
||||
# This is all to avoid an O(nm) brute force search.
|
||||
|
||||
conflicts = []
|
||||
paths.sort()
|
||||
for path, i in paths
|
||||
continue if i + 1 is paths.length
|
||||
nextPath = paths[i+1]
|
||||
if nextPath.startsWith path
|
||||
headDelta = (headPathMap[path] or headPathMap[nextPath])[0].delta
|
||||
pendingDelta = (pendingPathMap[path] or pendingPathMap[nextPath])[0].delta
|
||||
conflicts.push({headDelta: headDelta, pendingDelta: pendingDelta})
|
||||
pendingDelta.conflict = headDelta
|
||||
headDelta.conflict = pendingDelta
|
||||
offset = 1
|
||||
while i + offset < paths.length
|
||||
# Look at the neighbor
|
||||
nextPath = paths[i+offset]
|
||||
offset += 1
|
||||
|
||||
# these stop being substrings of each other? Then conflict DNE
|
||||
if not (nextPath.startsWith path) then break
|
||||
|
||||
# check if these two are from the same group, but we still need to check for more beyond
|
||||
unless headPathMap[path] or headPathMap[nextPath] then continue
|
||||
unless pendingPathMap[path] or pendingPathMap[nextPath] then continue
|
||||
|
||||
# Okay, we found two deltas from different groups which conflict
|
||||
for headMetaDelta in (headPathMap[path] or headPathMap[nextPath])
|
||||
headDelta = headMetaDelta.delta
|
||||
for pendingMetaDelta in (pendingPathMap[path] or pendingPathMap[nextPath])
|
||||
pendingDelta = pendingMetaDelta.delta
|
||||
conflicts.push({headDelta: headDelta, pendingDelta: pendingDelta})
|
||||
pendingDelta.conflict = headDelta
|
||||
headDelta.conflict = pendingDelta
|
||||
|
||||
return conflicts if conflicts.length
|
||||
|
||||
|
@ -126,12 +139,13 @@ groupDeltasByAffectingPaths = (deltas) ->
|
|||
metaDeltas = []
|
||||
for delta in deltas
|
||||
conflictPaths = []
|
||||
# We're being fairly liberal with what's a conflict, because the alternative is worse
|
||||
if delta.action is 'moved-index'
|
||||
# every other action affects just the data path, but moved indexes affect a swath
|
||||
indices = [delta.originalIndex, delta.destinationIndex]
|
||||
indices.sort()
|
||||
for index in _.range(indices[0], indices[1]+1)
|
||||
conflictPaths.push delta.dataPath.slice(0, delta.dataPath.length-1).concat(index)
|
||||
# If you moved items around in an array, mark the whole array as a gonner
|
||||
conflictPaths.push delta.dataPath.slice(0, delta.dataPath.length-1)
|
||||
else if delta.action in ['deleted', 'added'] and _.isNumber(delta.dataPath[delta.dataPath.length-1])
|
||||
# If you remove or add items in an array, mark the whole thing as a gonner
|
||||
conflictPaths.push delta.dataPath.slice(0, delta.dataPath.length-1)
|
||||
else
|
||||
conflictPaths.push delta.dataPath
|
||||
for path in conflictPaths
|
||||
|
@ -141,25 +155,7 @@ groupDeltasByAffectingPaths = (deltas) ->
|
|||
}
|
||||
|
||||
map = _.groupBy metaDeltas, 'path'
|
||||
|
||||
# Turns out there are cases where a single delta can include paths
|
||||
# that 'conflict' with each other, ie one is a substring of the other
|
||||
# because of moved indices. To handle this case, go through and prune
|
||||
# out all deeper paths that conflict with more shallow paths, so
|
||||
# getConflicts path checking works properly.
|
||||
|
||||
paths = _.keys(map)
|
||||
return map unless paths.length
|
||||
paths.sort()
|
||||
prunedMap = {}
|
||||
previousPath = paths[0]
|
||||
for path, i in paths
|
||||
continue if i is 0
|
||||
continue if path.startsWith previousPath
|
||||
prunedMap[path] = map[path]
|
||||
previousPath = path
|
||||
|
||||
prunedMap
|
||||
return map
|
||||
|
||||
module.exports.pruneConflictsFromDelta = (delta, conflicts) ->
|
||||
expandedDeltas = (conflict.pendingDelta for conflict in conflicts)
|
||||
|
|
|
@ -58,7 +58,9 @@ class CocoModel extends Backbone.Model
|
|||
@set(existing, {silent: true})
|
||||
CocoModel.backedUp[@id] = @
|
||||
|
||||
saveBackup: ->
|
||||
saveBackup: -> @saveBackupNow()
|
||||
|
||||
saveBackupNow: ->
|
||||
storage.save(@id, @attributes)
|
||||
CocoModel.backedUp[@id] = @
|
||||
|
||||
|
|
|
@ -107,7 +107,50 @@ UserSchema = c.object {},
|
|||
name: {type: 'string', maxLength: 30, title: 'Link Name', description: 'What are you linking to? Ex: "Personal Website", "GitHub"', format: 'link-name'}
|
||||
link: c.url {title: 'Link', description: 'The URL.', default: 'http://example.com'}
|
||||
photoURL: {type: 'string', format: 'image-file', title: 'Profile Picture', description: 'Upload a 256x256px or larger image if you want to show a different profile picture to employers than your normal avatar.'}
|
||||
|
||||
curated: c.object {title: 'Curated', required: ['shortDescription','mainTag','location','education','workHistory','phoneScreenFilter','schoolFilter','locationFilter','roleFilter','seniorityFilter']},
|
||||
shortDescription:
|
||||
title: 'Short description'
|
||||
description: 'A sentence or two describing the candidate'
|
||||
type: 'string'
|
||||
mainTag:
|
||||
title: 'Main tag'
|
||||
description: 'A main tag to describe this candidate'
|
||||
type: 'string'
|
||||
location:
|
||||
title: 'Location'
|
||||
description: "The CURRENT location of the candidate"
|
||||
type: 'string'
|
||||
education:
|
||||
title: 'Education'
|
||||
description: 'The main educational institution of the candidate'
|
||||
type: 'string'
|
||||
workHistory: c.array
|
||||
title: 'Work history'
|
||||
description: 'One or two places the candidate has worked'
|
||||
type: 'array'
|
||||
,
|
||||
title: 'Workplace'
|
||||
type: 'string'
|
||||
phoneScreenFilter:
|
||||
title: 'Phone screened'
|
||||
type: 'boolean'
|
||||
description: 'Whether the candidate has been phone screened.'
|
||||
schoolFilter:
|
||||
title: 'School'
|
||||
type: 'string'
|
||||
enum: ['Top 20 Eng.', 'Other US', 'Other Intl.']
|
||||
locationFilter:
|
||||
title: 'Location'
|
||||
type: 'string'
|
||||
enum: ['Bay Area', 'New York', 'Other US', 'International']
|
||||
roleFilter:
|
||||
title: 'Role'
|
||||
type: 'string'
|
||||
enum: ['Web Developer', 'Software Developer', 'iOS Developer', 'Android Developer', 'Project Manager']
|
||||
seniorityFilter:
|
||||
title: 'Seniority'
|
||||
type: 'string'
|
||||
enum: ['College Student', 'Recent Grad', 'Junior', 'Senior', 'Management']
|
||||
jobProfileApproved: {title: 'Job Profile Approved', type: 'boolean', description: 'Whether your profile has been approved by CodeCombat.'}
|
||||
jobProfileNotes: {type: 'string', maxLength: 1000, title: 'Our Notes', description: 'CodeCombat\'s notes on the candidate.', format: 'markdown', default: ''}
|
||||
employerAt: c.shortString {description: 'If given employer permissions to view job candidates, for which employer?'}
|
||||
|
|
|
@ -190,7 +190,7 @@ me.codeSnippet = (mode) ->
|
|||
return snippet =
|
||||
code: {type: 'string', title: 'Snippet', default: '', description: 'Code snippet. Use ${1:defaultValue} syntax to add flexible arguments'}
|
||||
# code: {type: 'string', format: 'ace', aceMode: 'ace/mode/'+mode, title: 'Snippet', default: '', description: 'Code snippet. Use ${1:defaultValue} syntax to add flexible arguments'}
|
||||
tab: {type: 'string', description: 'Tab completion text. Will be expanded to the snippet if typed and hit tab.'}
|
||||
tab: {type: 'string', title: 'Tab Trigger', description: 'Tab completion text. Will be expanded to the snippet if typed and hit tab.'}
|
||||
|
||||
me.activity = me.object {description: 'Stats on an activity'},
|
||||
first: me.date()
|
||||
|
|
|
@ -1,7 +1,61 @@
|
|||
#employers-view
|
||||
|
||||
h1, h2, h3
|
||||
font: Arial
|
||||
button
|
||||
background: #fce232 /* Old browsers */
|
||||
background: -moz-linear-gradient(top, #fce232 0%, #ea8e2b 100%)
|
||||
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#fce232), color-stop(100%,#ea8e2b))
|
||||
background: -webkit-linear-gradient(top, #fce232 0%,#ea8e2b 100%)
|
||||
background: -o-linear-gradient(top, #fce232 0%,#ea8e2b 100%)
|
||||
background: -ms-linear-gradient(top, #fce232 0%,#ea8e2b 100%)
|
||||
background: linear-gradient(to bottom, #fce232 0%,#ea8e2b 100%)
|
||||
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#fce232', endColorstr='#ea8e2b',GradientType=0 )
|
||||
vertical-align: text-bottom
|
||||
margin-left: 30px
|
||||
|
||||
//filter panels
|
||||
#filter
|
||||
margin-bottom: 10px
|
||||
.panel-heading
|
||||
background-color: darkgrey
|
||||
.panel-body
|
||||
background-color: darkgrey
|
||||
|
||||
#filters
|
||||
.filter_section
|
||||
width: 16%
|
||||
display: inline-block
|
||||
vertical-align: top
|
||||
margin-bottom: 10px
|
||||
.get-started-button
|
||||
vertical-align: text-bottom
|
||||
margin-left: 10px
|
||||
|
||||
#filter-button, #create-alert-button
|
||||
float: right
|
||||
|
||||
#login-link, #logout-button
|
||||
float: right
|
||||
color: #333333
|
||||
display: inline-block
|
||||
:visited
|
||||
color: #333333
|
||||
#logout-button:hover
|
||||
cursor: pointer
|
||||
|
||||
#tagline, .hiring-call-to-action
|
||||
width: 100%
|
||||
text-align: center
|
||||
display: inline-block
|
||||
margin-top: 20px
|
||||
h1, h2, h3
|
||||
display: inline-block
|
||||
button
|
||||
display: inline-block
|
||||
h1, h2, h3, h4
|
||||
font-family: Arial, Helvetica, sans-serif
|
||||
color: #333333
|
||||
#tagline
|
||||
margin-bottom: 20px
|
||||
|
||||
.see-candidates-header
|
||||
margin: 30px
|
||||
|
@ -10,23 +64,86 @@
|
|||
#see-candidates
|
||||
cursor: pointer
|
||||
|
||||
.employer_icon
|
||||
width: 125px
|
||||
float: left
|
||||
margin: 0px 15px 15px 0px
|
||||
.reasons
|
||||
height: 275px
|
||||
margin-bottom: 25px
|
||||
width: 100%
|
||||
|
||||
.information_row
|
||||
height: 150px
|
||||
padding-right: 15px
|
||||
.information_row
|
||||
height: 150px
|
||||
padding-right: 15px
|
||||
|
||||
#leftside
|
||||
width: 500px
|
||||
float: left
|
||||
|
||||
#rightside
|
||||
width: 500px
|
||||
float: left
|
||||
.reason
|
||||
width: 33%
|
||||
padding-left: 3%
|
||||
height: 150px
|
||||
display: inline-block
|
||||
vertical-align: top
|
||||
.employer_icon
|
||||
width: 125px
|
||||
margin: 0px auto
|
||||
|
||||
.reason_long
|
||||
width: 50%
|
||||
padding-left: 3%
|
||||
display: inline-block
|
||||
.employer_icon
|
||||
display: inline-block
|
||||
width: 25%
|
||||
max-width: 125px
|
||||
vertical-align: text-bottom
|
||||
.reason_text
|
||||
display: inline-block
|
||||
width: 75%
|
||||
#bottom_row
|
||||
height: auto
|
||||
#candidate-table
|
||||
width: 96%
|
||||
margin-left: 2%
|
||||
margin-right: 2%
|
||||
background-color: #E7E7E7
|
||||
table
|
||||
cursor: pointer
|
||||
width: 96%
|
||||
margin-left: 2%
|
||||
margin-right: 2%
|
||||
margin-bottom: 30px
|
||||
.tag_column
|
||||
width: 25%
|
||||
display: inline-block
|
||||
.location_column
|
||||
display: inline-block
|
||||
width: 25%
|
||||
.education_column
|
||||
display: inline-block
|
||||
width: 25%
|
||||
.work_column
|
||||
display: inline-block
|
||||
width: 25%
|
||||
tr
|
||||
.candidate-picture
|
||||
width: 50px
|
||||
height: 50px
|
||||
border-radius: 5px
|
||||
overflow: hidden
|
||||
margin-right: 10px
|
||||
|
||||
.candidate-description
|
||||
width: 100%
|
||||
vertical-align: bottom
|
||||
td
|
||||
margin-bottom: 10px
|
||||
margin-top: 10px
|
||||
padding-bottom: 5px
|
||||
padding-top: 10px
|
||||
.border_row
|
||||
border-bottom: 1px solid #d3d3d3
|
||||
vertical-align: bottom
|
||||
padding-top: 0px
|
||||
|
||||
#results
|
||||
display: inline-block
|
||||
.tablesorter
|
||||
//img
|
||||
// display: none
|
||||
|
@ -68,7 +185,7 @@
|
|||
|
||||
#employers-view, #profile-view.viewed-by-employer
|
||||
#outer-content-wrapper, #intermediate-content-wrapper, #inner-content-wrapper
|
||||
background: #949494
|
||||
background: #B4B4B4
|
||||
|
||||
.main-content-area
|
||||
background-color: #EAEAEA
|
||||
|
|
21
app/styles/recruitment_base.sass
Normal file
21
app/styles/recruitment_base.sass
Normal file
|
@ -0,0 +1,21 @@
|
|||
@import "bootstrap/variables"
|
||||
@import "bootstrap/mixins"
|
||||
@import "base"
|
||||
|
||||
#employers-wrapper
|
||||
background-color: #B4B4B4
|
||||
height: 100%
|
||||
#outer-content-wrapper, #intermediate-content-wrapper, #inner-content-wrapper
|
||||
background: #B4B4B4
|
||||
|
||||
.navbar, #top-nav, .content.clearfix
|
||||
background-color: #B4B4B4
|
||||
|
||||
.footer
|
||||
border-top: none
|
||||
background-color: #B4B4B4
|
||||
padding-bottom: 50px
|
||||
|
||||
|
||||
#employer-content-area
|
||||
margin: auto
|
|
@ -79,9 +79,6 @@ block content
|
|||
li
|
||||
a(href="/contribute#ambassador", data-i18n="classes.ambassador_title")
|
||||
| : support our community of educators and coders.
|
||||
li
|
||||
a(href="/contribute#counselor", data-i18n="classes.counselor_title")
|
||||
| : offer your advice and business acumen to the founders.
|
||||
|
||||
| Check out the
|
||||
a(href="/contribute", data-i18n="nav.contribute")
|
||||
|
|
|
@ -179,23 +179,4 @@ block content
|
|||
|
||||
.contributor-signup(data-contributor-class-id="support", data-contributor-class-name="ambassador")
|
||||
|
||||
#counselor.header-scrolling-fix
|
||||
|
||||
.class_image
|
||||
img.img-responsive(src="/images/pages/contribute/counselor.png", alt="")
|
||||
|
||||
h3.header-scrolling-fix
|
||||
span(data-i18n="classes.counselor_title") Counselor
|
||||
span
|
||||
span(data-i18n="classes.counselor_title_description") (Expert/Teacher)
|
||||
p(data-i18n="contribute.counselor_summary")
|
||||
| None of the above roles fit what you are interested in? Do not worry, we are on the
|
||||
| lookout for anybody who wants a hand in the development of CodeCombat! If you are
|
||||
| interested in teaching, game development, open source management, or anything else
|
||||
| that you think will be relevant to us, then this class is for you.
|
||||
|
||||
a(href="/contribute/counselor")
|
||||
p.lead(data-i18n="contribute.more_about_counselor")
|
||||
| Learn More About Becoming a Counselor
|
||||
|
||||
div.clearfix
|
|
@ -31,8 +31,3 @@ ul.contribute_class.affix.nav.nav-list.nav-pills#contribute-nav
|
|||
span(data-i18n="classes.ambassador_title") Ambassador
|
||||
span
|
||||
span(data-i18n="classes.ambassador_title_description") (Support)
|
||||
li
|
||||
a(href=navPrefix + "#counselor")
|
||||
span(data-i18n="classes.counselor_title") Counselor
|
||||
span
|
||||
span(data-i18n="classes.counselor_title_description") (Expert/Teacher)
|
|
@ -1,49 +0,0 @@
|
|||
extends /templates/base
|
||||
|
||||
block content
|
||||
|
||||
div.contribute_class
|
||||
|
||||
include /templates/contribute/contribute_nav
|
||||
|
||||
div.class-main#sounselor-main
|
||||
|
||||
.class_image
|
||||
img.img-responsive(src="/images/pages/contribute/counselor.png", alt="")
|
||||
|
||||
h2
|
||||
span(data-i18n="classes.counselor_title") Counselor
|
||||
span
|
||||
span(data-i18n="classes.counselor_title_description") (Expert/Teacher)
|
||||
p(data-i18n="contribute.counselor_introduction_1")
|
||||
| Do you have life experience?
|
||||
| A different perspective on things that can help us decide how to shape CodeCombat?
|
||||
| Of all these roles, this will probably take the least time, but
|
||||
| individually you may make the most difference.
|
||||
| We're on the lookout for wisened sages, particularly in areas like: teaching,
|
||||
| game development, open source project management, technical recruiting, entrepreneurship,
|
||||
| or design.
|
||||
|
||||
p(data-i18n="contribute.counselor_introduction_2")
|
||||
| Or really anything that is relevant to the development of CodeCombat.
|
||||
| If you have knowledge and want to share it to help grow this project, then
|
||||
| this class might be for you.
|
||||
|
||||
h4(data-i18n="contribute.class_attributes") Class Attributes
|
||||
ul
|
||||
li(data-i18n="contribute.counselor_attribute_1")
|
||||
| Experience, in any of the areas above or something you think might be helpful.
|
||||
li(data-i18n="contribute.counselor_attribute_2")
|
||||
| A little bit of free time!
|
||||
|
||||
h4(data-i18n="contribute.how_to_join") How to Join
|
||||
p
|
||||
a(title='Contact', tabindex=-1, data-toggle="coco-modal", data-target="modal/contact", data-i18n="contribute.contact_us_url")
|
||||
| Contact us
|
||||
span ,
|
||||
span(data-i18n="contribute.counselor_join_desc")
|
||||
| tell us a little about yourself, what you've done and what you'd
|
||||
| be interested in doing. We'll put you in our contact list and be in touch
|
||||
| when we could use advice (not too often).
|
||||
|
||||
div.clearfix
|
|
@ -1,140 +1,175 @@
|
|||
extends /templates/base
|
||||
extends /templates/recruitment_base
|
||||
|
||||
block content
|
||||
if me.get('anonymous')
|
||||
a#login-link Login
|
||||
br
|
||||
if !isEmployer && !me.isAdmin()
|
||||
#tagline
|
||||
h1(data-i18n="employers.hire_developers_not_credentials") Hire developers, not credentials.
|
||||
button.btn.get-started-button Get started
|
||||
else
|
||||
if !me.get('anonymous')
|
||||
a#logout-button(data-i18n="login.log_out") Logout
|
||||
br
|
||||
#filter
|
||||
.panel-group#filter_panel
|
||||
a#filter-link(data-toggle="collapse" data-target="#collapseOne")
|
||||
.panel.panel-default
|
||||
.panel-heading
|
||||
h4.panel-title
|
||||
|
||||
h1(data-i18n="employers.want_to_hire_our_players") Hire CodeCombat Players
|
||||
span.glyphicon.glyphicon-folder-open#folder-icon
|
||||
| Filter
|
||||
.panel-collapse.collapse.in#collapseOne
|
||||
.panel-body
|
||||
form#filters
|
||||
.filter_section#screened_filter
|
||||
h4 Screened
|
||||
input(type="checkbox" name="phoneScreenFilter" value="true")
|
||||
| Phone Screened
|
||||
br
|
||||
input(type="checkbox" name="phoneScreenFilter" value="false")
|
||||
| Not Phone Screened
|
||||
.filter_section#visa_filter
|
||||
h4 Visa
|
||||
input(type="checkbox" name="visa" value="Authorized to work in the US")
|
||||
| US Authorized
|
||||
br
|
||||
input(type="checkbox" name="visa" value="Need visa sponsorship")
|
||||
| Not Authorized
|
||||
.filter_section#school_filter
|
||||
h4 School
|
||||
input(type="checkbox" name="schoolFilter" value="Top 20 Eng.")
|
||||
| Top 20 Eng.
|
||||
br
|
||||
input(type="checkbox" name="schoolFilter" value="Other US")
|
||||
| Other US
|
||||
br
|
||||
input(type="checkbox" name="schoolFilter" value="Other Intl.")
|
||||
| Other Intl.
|
||||
.filter_section#location_filter
|
||||
h4 Location
|
||||
input(type="checkbox" name="locationFilter" value="Bay Area")
|
||||
| Bay Area
|
||||
br
|
||||
input(type="checkbox" name="locationFilter" value="New York")
|
||||
| New York
|
||||
br
|
||||
input(type="checkbox" name="locationFilter" value="Other US")
|
||||
| Other US
|
||||
br
|
||||
input(type="checkbox" name="locationFilter" value="International")
|
||||
| International
|
||||
.filter_section#role_filter
|
||||
h4 Role
|
||||
input(type="checkbox" name="roleFilter" value="Web Developer")
|
||||
| Web Developer
|
||||
br
|
||||
input(type="checkbox" name="roleFilter" value="Software Developer")
|
||||
| Software Developer
|
||||
br
|
||||
input(type="checkbox" name="roleFilter" value="iOS Developer")
|
||||
| iOS Developer
|
||||
br
|
||||
input(type="checkbox" name="roleFilter" value="Android Developer")
|
||||
| Android Developer
|
||||
br
|
||||
input(type="checkbox" name="roleFilter" value="Project Manager")
|
||||
| Project Developer
|
||||
.filter_section#seniority_filter
|
||||
h4 Seniority
|
||||
input(type="checkbox" name="seniorityFilter" value="College Student")
|
||||
| College Student
|
||||
br
|
||||
input(type="checkbox" name="seniorityFilter" value="Recent Grad")
|
||||
| Recent Grad
|
||||
br
|
||||
input(type="checkbox" name="seniorityFilter" value="Junior")
|
||||
| Junior
|
||||
br
|
||||
input(type="checkbox" name="seniorityFilter" value="Senior")
|
||||
| Senior
|
||||
br
|
||||
input(type="checkbox" name="seniorityFilter" value="Management")
|
||||
| Management
|
||||
//input#select_all_checkbox(type="checkbox" name="select_all" checked)
|
||||
//| Select all
|
||||
button.btn#filter-button Filter
|
||||
p#results #{numberOfCandidates} results
|
||||
//button.btn#create-alert-button Create Alert
|
||||
if candidates.length
|
||||
#candidate-table
|
||||
table
|
||||
tbody
|
||||
for candidate, index in featuredCandidates
|
||||
- var profile = candidate.get('jobProfile');
|
||||
- var authorized = candidate.id; // If we have the id, then we are authorized.
|
||||
- var profileAge = (new Date() - new Date(profile.updated)) / 86400 / 1000;
|
||||
- var expired = profileAge > 2 * 30.4;
|
||||
- var curated = profile.curated;
|
||||
|
||||
tr.candidate-row(data-candidate-id=candidate.id, id=candidate.id, class=expired ? "expired" : "")
|
||||
td(rowspan=2)
|
||||
.candidate-picture
|
||||
img(src=candidate.getPhotoURL(50), alt=profile.name, title=profile.name, width=50)
|
||||
if curated && curated.shortDescription
|
||||
td.candidate-description #{curated.shortDescription}
|
||||
else
|
||||
td.candidate-description #{profile.shortDescription}
|
||||
tr.border_row(data-candidate-id=candidate.id)
|
||||
if curated
|
||||
- var workHistory = curated.workHistory.join(",");
|
||||
td.tag_column
|
||||
img(src="/images/pages/employer/tag.png")
|
||||
| #{curated.mainTag}
|
||||
td.location_column
|
||||
img(src="/images/pages/employer/location.png")
|
||||
| #{curated.location}
|
||||
td.education_column
|
||||
img(src="/images/pages/employer/education.png")
|
||||
| #{curated.education}
|
||||
td.work_column
|
||||
img(src="/images/pages/employer/briefcase.png")
|
||||
| #{workHistory}
|
||||
else
|
||||
td Hi
|
||||
|
||||
if !isEmployer && !me.isAdmin()
|
||||
div#info_wrapper
|
||||
span.hiring-call-to-action
|
||||
h2#start-hiring(data-i18n="employers.start_hiring") Start hiring.
|
||||
button.btn.get-started-button Get started
|
||||
|
||||
div#leftside
|
||||
|
||||
div.information_row
|
||||
|
||||
img(class="employer_icon" src="/images/pages/employer/employer_icon1.png")
|
||||
|
||||
h2(data-i18n="employers.what") What is CodeCombat?
|
||||
|
||||
p(data-i18n="employers.what_blurb") CodeCombat is a multiplayer browser programming game. Players write code to control their forces in battle against other developers. We support JavaScript, Python, Lua, Clojure, CoffeeScript, and Io.
|
||||
|
||||
div.information_row
|
||||
|
||||
h2#hiring-reasons.hiring-call-to-action(data-i18n="employers.reasons") 3 reasons you should hire through us:
|
||||
.reasons#top_row
|
||||
.reason
|
||||
img.employer_icon(src="/images/pages/employer/employer_icon2.png")
|
||||
h3(data-i18n="employers.everyone_looking") Everyone here is looking for work.
|
||||
p(data-i18n="employers.everyone_looking_blurb") Forget about 20% LinkedIn InMail response rates. Everyone that we list on this site wants to find their next position and will respond to your request for an introduction.
|
||||
.reason
|
||||
img.employer_icon(src="/images/pages/employer/employer_icon6.png")
|
||||
h3(data-i18n="employers.weeding") We've done the weeding for you.
|
||||
//this will break in i18n. Fix the inlining
|
||||
p(data-i18n="employers.weeding_blurb")
|
||||
| Every candidate that has a
|
||||
span.glyphicon.glyphicon-earphone
|
||||
| icon has already gone through a phone screen with us. We only feature developers that we would work with.
|
||||
.reason
|
||||
img(class="employer_icon" src="/images/pages/employer/employer_icon3.png")
|
||||
|
||||
h2(data-i18n="employers.who") Who Are the Players?
|
||||
|
||||
p(data-i18n="employers.who_blurb") CodeCombateers are CTOs, VPs of Engineering, and graduates of top 20 engineering schools. No junior developers here. Our players enjoy playing with code and solving problems.
|
||||
|
||||
div.information_row
|
||||
|
||||
img(class="employer_icon" src="/images/pages/employer/employer_icon5.png")
|
||||
|
||||
h2(data-i18n="employers.cost") Who Are the Players?
|
||||
|
||||
p(data-i18n="employers.cost_blurb") CodeCombateers are CTOs, VPs of Engineering, and graduates of top 20 engineering schools. No junior developers here. Our players enjoy playing with code and solving problems.
|
||||
|
||||
div#rightside
|
||||
|
||||
div.information_row
|
||||
|
||||
img(class="employer_icon" src="/images/pages/employer/employer_icon2.png")
|
||||
|
||||
h2(data-i18n="employers.how") How Much Do we Charge?
|
||||
|
||||
p(data-i18n="employers.how_blurb") We charge 15% of first year's salary and offer a 100% money back guarantee for 90 days. We don't charge for candidates who are already actively being interviewed at your company.
|
||||
|
||||
div.information_row
|
||||
|
||||
img(class="employer_icon" src="/images/pages/employer/employer_icon4.png")
|
||||
|
||||
h2(data-i18n="employers.why") Why Hire Through Us?
|
||||
|
||||
p
|
||||
span(data-i18n="employers.why_blurb_1") We will save you time. Every CodeCombateer we feaure is
|
||||
strong(data-i18n="employers.why_blurb_2") looking for work
|
||||
span(data-i18n="employers.why_blurb_3") , has
|
||||
strong(data-i18n="employers.why_blurb_4") demonstrated top notch technical skills
|
||||
span(data-i18n="employers.why_blurb_5") , and has been
|
||||
strong(data-i18n="employers.why_blurb_6") personally screened by us
|
||||
span(data-i18n="employers.why_blurb_7") . Stop screening and start hiring.
|
||||
|
||||
div.information_row
|
||||
|
||||
img(class="employer_icon" src="/images/pages/employer/employer_icon6.png")
|
||||
|
||||
h2(data-i18n="employers.response") What's the Response Rate?
|
||||
|
||||
p(data-i18n="employers.response_blurb") Almost every developer you contact on CodeCombat will respond to inquires whether or not they want to interivew. If you would like help finding a candidate for your role, we can make recommendations.
|
||||
|
||||
if candidates.length
|
||||
|
||||
ul.nav.nav-pills
|
||||
li.active
|
||||
a(href="#featured-candidates", data-toggle="tab")
|
||||
span(data-i18n="employers.featured_developers") Featured Developers
|
||||
| (#{featuredCandidates.length})
|
||||
if otherCandidates.length
|
||||
li
|
||||
a(href="#other-candidates", data-toggle="tab")
|
||||
span(data-i18n="employers.other_developers") Other Developers
|
||||
| (#{otherCandidates.length})
|
||||
if me.isAdmin() && inactiveCandidates.length
|
||||
li
|
||||
a(href="#inactive-candidates", data-toggle="tab")
|
||||
span(data-i18n="employers.inactive_developers") Inactive Developers
|
||||
| (#{inactiveCandidates.length})
|
||||
|
||||
div.tab-content
|
||||
for area, tabIndex in [{id: "featured-candidates", candidates: featuredCandidates}, {id: "other-candidates", candidates: otherCandidates}, {id: "inactive-candidates", candidates: inactiveCandidates}]
|
||||
div(class="tab-pane well" + (tabIndex ? "" : " active"), id=area.id)
|
||||
table.table.table-condensed.table-hover.table-responsive.tablesorter
|
||||
thead
|
||||
tr
|
||||
th(data-i18n="general.name") Name
|
||||
th(data-i18n="employers.candidate_location") Location
|
||||
th(data-i18n="employers.candidate_looking_for") Looking For
|
||||
th(data-i18n="employers.candidate_role") Role
|
||||
th(data-i18n="employers.candidate_top_skills") Top Skills
|
||||
th(data-i18n="employers.candidate_years_experience") Yrs Exp
|
||||
th(data-i18n="employers.candidate_last_updated") Last Updated
|
||||
if me.isAdmin()
|
||||
th(data-i18n="employers.candidate_who") Who
|
||||
if me.isAdmin() && area.id == 'inactive-candidates'
|
||||
th ✓?
|
||||
|
||||
tbody
|
||||
for candidate, index in area.candidates
|
||||
- var profile = candidate.get('jobProfile');
|
||||
- var authorized = candidate.id; // If we have the id, then we are authorized.
|
||||
- var profileAge = (new Date() - new Date(profile.updated)) / 86400 / 1000;
|
||||
- var expired = profileAge > 2 * 30.4;
|
||||
tr(data-candidate-id=candidate.id, id=candidate.id, class=expired ? "expired" : "")
|
||||
td
|
||||
if authorized
|
||||
img(src=candidate.getPhotoURL(50), alt=profile.name, title=profile.name, height=50)
|
||||
if profile.name
|
||||
p= profile.name
|
||||
else if me.isAdmin()
|
||||
p (#{candidate.get('name')})
|
||||
else
|
||||
img(src="/images/pages/contribute/archmage.png", alt="", title="Sign up as an employer to see our candidates", width=50)
|
||||
p Developer ##{index + 1 + (area.id == 'featured-candidates' ? 0 : featuredCandidates.length)}
|
||||
if profile.country == 'USA'
|
||||
td= profile.city
|
||||
else
|
||||
td= profile.country
|
||||
td= profile.lookingFor
|
||||
td= profile.jobTitle
|
||||
td
|
||||
each skill in profile.skills
|
||||
code= skill
|
||||
span
|
||||
td= profile.experience
|
||||
td(data-profile-age=profileAge)= moment(profile.updated).fromNow()
|
||||
if me.isAdmin()
|
||||
td= remarks[candidate.id] ? remarks[candidate.id].get('contactName') : ''
|
||||
if me.isAdmin() && area.id == 'inactive-candidates'
|
||||
if candidate.get('jobProfileApproved')
|
||||
td ✓
|
||||
else
|
||||
td ✗
|
||||
h3(data-i18n="employers.pass_screen") They will pass your technical screen.
|
||||
p(data-i18n="employers.pass_screen_blurb") All of these developers have ranked in our programming competitions. One employer found that 5x as many of our devs passed their technical screen than hiring from Hacker News.
|
||||
span.hiring-call-to-action
|
||||
h2(data-i18n="employers.make_hiring_easier") Make my hiring easier, please.
|
||||
button.btn.get-started-button Get started
|
||||
.reasons#bottom_row
|
||||
.reason_long
|
||||
img.employer_icon(src="/images/pages/employer/employer_icon1.png")
|
||||
.reason_text
|
||||
h3(data-i18n="employers.what") What is CodeCombat?
|
||||
p(data-i18n="employers.what_blurb") CodeCombat is a multiplayer browser programming game. Players write code to control their forces in battle against other developers. We support JavaScript, Python, Lua, Clojure, CoffeeScript, and Io.
|
||||
.reason_long
|
||||
img.employer_icon(src="/images/pages/employer/employer_icon5.png")
|
||||
.reason_text
|
||||
h3(data-i18n="employers.cost") Who Are the Players?
|
||||
p(data-i18n="employers.cost_blurb") CodeCombateers are CTOs, VPs of Engineering, and graduates of top 20 engineering schools. No junior developers here. Our players enjoy playing with code and solving problems.
|
||||
|
|
21
app/templates/recruitment_base.jade
Normal file
21
app/templates/recruitment_base.jade
Normal file
|
@ -0,0 +1,21 @@
|
|||
body
|
||||
#fb-root
|
||||
#employers-wrapper
|
||||
block header
|
||||
.nav
|
||||
.content.clearfix
|
||||
.navbar-header
|
||||
a.navbar-brand(href='/')
|
||||
img(src="/images/pages/base/recruitment_logo.png", title="CodeCombat - Learn how to code by playing a game", alt="CodeCombat")
|
||||
block outer_content
|
||||
#outer-content-wrapper
|
||||
|
||||
#intermediate-content-wrapper
|
||||
|
||||
#inner-content-wrapper
|
||||
.main-content-area#employer-content-area
|
||||
block content
|
||||
p If this is showing, you dun goofed
|
||||
|
||||
block footer
|
||||
.footer
|
|
@ -73,7 +73,7 @@ module.exports = class EditorLevelView extends View
|
|||
Backbone.Mediator.publish 'level-loaded', level: @level
|
||||
@showReadOnly() if me.get('anonymous')
|
||||
@patchesView = @insertSubView(new PatchesView(@level), @$el.find('.patches-view'))
|
||||
@listenTo @patchesView, 'accepted-patch', -> setTimeout 'location.reload()', 400
|
||||
@listenTo @patchesView, 'accepted-patch', -> location.reload()
|
||||
@$el.find('#level-watch-button').find('> span').toggleClass('secret') if @level.watching()
|
||||
|
||||
onPlayLevel: (e) ->
|
||||
|
|
|
@ -57,6 +57,7 @@ module.exports = class PatchModal extends ModalView
|
|||
acceptPatch: ->
|
||||
delta = @deltaView.getApplicableDelta()
|
||||
@targetModel.applyDelta(delta)
|
||||
@targetModel.saveBackupNow()
|
||||
@patch.setStatus('accepted')
|
||||
@trigger 'accepted-patch'
|
||||
@hide()
|
||||
|
|
|
@ -21,10 +21,19 @@ module.exports = class EmployersView extends View
|
|||
|
||||
events:
|
||||
'click tbody tr': 'onCandidateClicked'
|
||||
'change #filters input': 'onFilterChanged'
|
||||
'click #filter-button': 'applyFilters'
|
||||
'change #select_all_checkbox': 'handleSelectAllChange'
|
||||
'click .get-started-button': 'openSignupModal'
|
||||
'click .navbar-brand': 'restoreBodyColor'
|
||||
'click #login-link': 'onClickAuthbutton'
|
||||
'click #filter-link': 'swapFolderIcon'
|
||||
|
||||
constructor: (options) ->
|
||||
super options
|
||||
@getCandidates()
|
||||
@setFilterDefaults()
|
||||
|
||||
|
||||
afterRender: ->
|
||||
super()
|
||||
|
@ -33,19 +42,98 @@ module.exports = class EmployersView extends View
|
|||
afterInsert: ->
|
||||
super()
|
||||
_.delay @checkForEmployerSignupHash, 500
|
||||
#fairly hacky, change this in the future
|
||||
@originalBackgroundColor = $('body').css 'background-color'
|
||||
$('body').css 'background-color', '#B4B4B4'
|
||||
|
||||
restoreBodyColor: ->
|
||||
$('body').css 'background-color', @originalBackgroundColor
|
||||
|
||||
swapFolderIcon: ->
|
||||
$('#folder-icon').toggleClass('glyphicon-folder-close').toggleClass('glyphicon-folder-open')
|
||||
onFilterChanged: ->
|
||||
@resetFilters()
|
||||
that = @
|
||||
$('#filters :input').each ->
|
||||
input = $(this)
|
||||
checked = input.prop 'checked'
|
||||
name = input.attr 'name'
|
||||
value = input.val()
|
||||
if name is 'phoneScreenFilter'
|
||||
value = JSON.parse(input.prop 'value')
|
||||
if checked
|
||||
that.filters[name] = _.union that.filters[name], [value]
|
||||
else
|
||||
that.filters[name] = _.difference that.filters[name], [value]
|
||||
|
||||
for filterName, filterValues of @filters
|
||||
if filterValues.length is 0
|
||||
@filters[filterName] = @defaultFilters[filterName]
|
||||
|
||||
openSignupModal: ->
|
||||
@openModalView new EmployerSignupView
|
||||
handleSelectAllChange: (e) ->
|
||||
checkedState = e.currentTarget.checked
|
||||
$('#filters :input').each ->
|
||||
$(this).prop 'checked', checkedState
|
||||
@onFilterChanged()
|
||||
|
||||
resetFilters: ->
|
||||
for filterName, filterValues of @filters
|
||||
@filters[filterName] = []
|
||||
|
||||
applyFilters: ->
|
||||
candidateList = _.sortBy @candidates.models, (c) -> c.get('jobProfile').updated
|
||||
candidateList = _.filter candidateList, (c) -> c.get('jobProfileApproved')
|
||||
|
||||
filteredCandidates = candidateList
|
||||
for filterName, filterValues of @filters
|
||||
if filterName is 'visa'
|
||||
filteredCandidates = _.difference filteredCandidates, _.filter(filteredCandidates, (c) ->
|
||||
fieldValue = c.get('jobProfile').visa
|
||||
return not (_.contains filterValues, fieldValue)
|
||||
)
|
||||
else
|
||||
filteredCandidates = _.difference filteredCandidates, _.filter(filteredCandidates, (c) ->
|
||||
unless c.get('jobProfile').curated then return true
|
||||
fieldValue = c.get('jobProfile').curated?[filterName]
|
||||
return not (_.contains filterValues, fieldValue)
|
||||
)
|
||||
candidateIDsToShow = _.pluck filteredCandidates, 'id'
|
||||
$('#candidate-table tr').each -> $(this).hide()
|
||||
candidateIDsToShow.forEach (id) ->
|
||||
$("[data-candidate-id=#{id}]").show()
|
||||
$('#results').text(candidateIDsToShow.length + ' results')
|
||||
|
||||
|
||||
return filteredCandidates
|
||||
setFilterDefaults: ->
|
||||
@filters =
|
||||
phoneScreenFilter: [true, false]
|
||||
visa: ['Authorized to work in the US', 'Need visa sponsorship']
|
||||
schoolFilter: ['Top 20 Eng.', 'Other US', 'Other Intl.']
|
||||
locationFilter: ['Bay Area', 'New York', 'Other US', 'International']
|
||||
roleFilter: ['Web Developer', 'Software Developer', 'iOS Developer', 'Android Developer', 'Project Manager']
|
||||
seniorityFilter: ['College Student', 'Recent Grad', 'Junior', 'Senior', 'Management']
|
||||
@defaultFilters = _.cloneDeep @filters
|
||||
|
||||
|
||||
getRenderData: ->
|
||||
ctx = super()
|
||||
ctx.isEmployer = @isEmployer()
|
||||
ctx.candidates = _.sortBy @candidates.models, (c) -> c.get('jobProfile').updated
|
||||
ctx.candidates = _.sortBy @candidates.models, (c) -> -1 * c.get('jobProfile').experience
|
||||
ctx.activeCandidates = _.filter ctx.candidates, (c) -> c.get('jobProfile').active
|
||||
ctx.inactiveCandidates = _.reject ctx.candidates, (c) -> c.get('jobProfile').active
|
||||
ctx.featuredCandidates = _.filter ctx.activeCandidates, (c) -> c.get('jobProfileApproved')
|
||||
unless @isEmployer() or me.isAdmin()
|
||||
ctx.featuredCandidates = _.filter ctx.featuredCandidates, (c) -> c.get('jobProfile').curated
|
||||
ctx.featuredCandidates = ctx.featuredCandidates.slice(0,7)
|
||||
ctx.otherCandidates = _.reject ctx.activeCandidates, (c) -> c.get('jobProfileApproved')
|
||||
ctx.remarks = {}
|
||||
ctx.remarks[remark.get('user')] = remark for remark in @remarks.models
|
||||
ctx.moment = moment
|
||||
ctx._ = _
|
||||
ctx.numberOfCandidates = ctx.featuredCandidates.length
|
||||
ctx
|
||||
|
||||
isEmployer: ->
|
||||
|
@ -64,10 +152,10 @@ module.exports = class EmployersView extends View
|
|||
renderCandidatesAndSetupScrolling: =>
|
||||
@render()
|
||||
$('.nano').nanoScroller()
|
||||
if window.history?.state?.lastViewedCandidateID
|
||||
$('.nano').nanoScroller({scrollTo: $('#' + window.history.state.lastViewedCandidateID)})
|
||||
else if window.location.hash.length is 25
|
||||
$('.nano').nanoScroller({scrollTo: $(window.location.hash)})
|
||||
#if window.history?.state?.lastViewedCandidateID
|
||||
# $('.nano').nanoScroller({scrollTo: $('#' + window.history.state.lastViewedCandidateID)})
|
||||
#else if window.location.hash.length is 25
|
||||
# $('.nano').nanoScroller({scrollTo: $(window.location.hash)})
|
||||
|
||||
checkForEmployerSignupHash: =>
|
||||
if window.location.hash is '#employerSignupLoggingIn' and not ('employer' in me.get('permissions'))
|
||||
|
@ -193,7 +281,7 @@ module.exports = class EmployersView extends View
|
|||
|
||||
onCandidateClicked: (e) ->
|
||||
id = $(e.target).closest('tr').data('candidate-id')
|
||||
if id
|
||||
if id and (@isEmployer() or me.isAdmin())
|
||||
if window.history
|
||||
oldState = _.cloneDeep window.history.state ? {}
|
||||
oldState['lastViewedCandidateID'] = id
|
||||
|
|
|
@ -93,7 +93,7 @@ module.exports = class SpellPaletteView extends View
|
|||
tabbify = count >= 10
|
||||
@entries = []
|
||||
|
||||
Backbone.Mediator.publish 'tome:update-snippets', propGroups: propGroups, allDocs: allDocs
|
||||
Backbone.Mediator.publish 'tome:update-snippets', propGroups: propGroups, allDocs: allDocs, language: @options.language
|
||||
|
||||
for owner, props of propGroups
|
||||
for prop in props
|
||||
|
|
|
@ -96,6 +96,7 @@ module.exports = class SpellView extends View
|
|||
@aceSession.selection.on 'changeCursor', @onCursorActivity
|
||||
$(@ace.container).find('.ace_gutter').on 'click', '.ace_error, .ace_warning, .ace_info', @onAnnotationClick
|
||||
@zatanna = new Zatanna @ace,
|
||||
|
||||
liveCompletion: aceConfig.liveCompletion ? true
|
||||
completers:
|
||||
keywords: false
|
||||
|
@ -180,16 +181,17 @@ module.exports = class SpellView extends View
|
|||
return (owner is 'this' or owner is 'more') and (not doc.owner? or doc.owner is 'this')
|
||||
console.log 'could not find doc for', prop, 'from', e.allDocs['__' + prop], 'for', owner, 'of', e.propGroups unless doc
|
||||
doc ?= prop
|
||||
if doc.snippets?[@spell.language]
|
||||
if doc.snippets?[e.language]
|
||||
entry =
|
||||
content: doc.snippets[@spell.language].code
|
||||
content: doc.snippets[e.language].code
|
||||
name: doc.name
|
||||
tabTrigger: doc.snippets[@spell.language].tab
|
||||
tabTrigger: doc.snippets[e.language].tab
|
||||
snippetEntries.push entry
|
||||
|
||||
window.zatanna = @zatanna
|
||||
window.snippetEntries = snippetEntries
|
||||
@zatanna.addSnippets snippetEntries, @spell.language
|
||||
# window.zatanna = @zatanna
|
||||
# window.snippetEntries = snippetEntries
|
||||
lang = @editModes[e.language].substr 'ace/mode/'.length
|
||||
@zatanna.addSnippets snippetEntries, lang
|
||||
|
||||
onMultiplayerChanged: ->
|
||||
if @session.get('multiplayer')
|
||||
|
@ -652,7 +654,7 @@ module.exports = class SpellView extends View
|
|||
onChangeLanguage: (e) ->
|
||||
return unless @spell.canWrite()
|
||||
@aceSession.setMode @editModes[e.language]
|
||||
@zatanna.set 'language', @editModes[e.language].substr('ace/mode/')
|
||||
# @zatanna.set 'language', @editModes[e.language].substr('ace/mode/')
|
||||
wasDefault = @getSource() is @spell.originalSource
|
||||
@spell.setLanguage e.language
|
||||
@reloadCode true if wasDefault
|
||||
|
|
|
@ -44,7 +44,7 @@
|
|||
"bootstrap": "~3.1.1",
|
||||
"validated-backbone-mediator": "~0.1.3",
|
||||
"jquery.browser": "~0.0.6",
|
||||
"zatanna": "~0.0.1"
|
||||
"zatanna": "~0.0.2"
|
||||
},
|
||||
"overrides": {
|
||||
"backbone": {
|
||||
|
|
|
@ -321,10 +321,10 @@ UserHandler = class UserHandler extends Handler
|
|||
@sendSuccess(res, candidates)
|
||||
|
||||
formatCandidate: (authorized, document) ->
|
||||
fields = if authorized then ['name', 'jobProfile', 'jobProfileApproved', 'photoURL', '_id'] else ['jobProfile', 'jobProfileApproved']
|
||||
fields = if authorized then ['name', 'jobProfile', 'jobProfileApproved', 'photoURL', '_id'] else ['_id','jobProfile', 'jobProfileApproved']
|
||||
obj = _.pick document.toObject(), fields
|
||||
obj.photoURL ||= obj.jobProfile.photoURL if authorized
|
||||
subfields = ['country', 'city', 'lookingFor', 'jobTitle', 'skills', 'experience', 'updated', 'active']
|
||||
obj.photoURL ||= obj.jobProfile.photoURL #if authorized
|
||||
subfields = ['country', 'city', 'lookingFor', 'jobTitle', 'skills', 'experience', 'updated', 'active', 'shortDescription', 'curated', 'visa']
|
||||
if authorized
|
||||
subfields = subfields.concat ['name']
|
||||
obj.jobProfile = _.pick obj.jobProfile, subfields
|
||||
|
|
18
test/app/lib/deltas.spec.coffee
Normal file
18
test/app/lib/deltas.spec.coffee
Normal file
|
@ -0,0 +1,18 @@
|
|||
deltas = require 'lib/deltas'
|
||||
|
||||
describe 'deltas lib', ->
|
||||
|
||||
describe 'getConflicts', ->
|
||||
|
||||
it 'handles conflicts where one change conflicts with several changes', ->
|
||||
originalData = {list:[1,2,3]}
|
||||
forkA = {list:['1', 2, '3']}
|
||||
forkB = {noList: '...'}
|
||||
differ = deltas.makeJSONDiffer()
|
||||
|
||||
expandedDeltaA = deltas.expandDelta(differ.diff originalData, forkA)
|
||||
expandedDeltaB = deltas.expandDelta(differ.diff originalData, forkB)
|
||||
deltas.getConflicts(expandedDeltaA, expandedDeltaB)
|
||||
for delta in expandedDeltaA
|
||||
expect(delta.conflict).toBeDefined()
|
||||
|
Loading…
Reference in a new issue