mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-04-27 06:23:41 -04:00
Merge branch 'master' of https://github.com/codecombat/codecombat
This commit is contained in:
commit
2b4b91733c
13 changed files with 488 additions and 91 deletions
app
server/routes
|
@ -82,7 +82,7 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
|
|||
if @thangType.isFullyLoaded()
|
||||
@setupSprite()
|
||||
else
|
||||
@thangType.fetch()
|
||||
@thangType.fetch() unless @thangType.loading
|
||||
@listenToOnce(@thangType, 'sync', @setupSprite)
|
||||
|
||||
setupSprite: ->
|
||||
|
@ -442,7 +442,7 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
|
|||
console.warn 'Cannot show action', action, 'for', @thangType.get('name'), 'because it DNE' unless @warnedFor[action]
|
||||
@warnedFor[action] = true
|
||||
return if @action is 'idle' then null else 'idle'
|
||||
action = 'break' if @actions.break? and @thang?.erroredOut
|
||||
#action = 'break' if @actions.break? and @thang?.erroredOut # This makes it looks like it's dead when it's not: bad in Brawlwood.
|
||||
action = 'die' if @actions.die? and thang?.health? and thang.health <= 0
|
||||
@actions[action]
|
||||
|
||||
|
|
|
@ -115,7 +115,8 @@ module.exports = Surface = class Surface extends CocoClass
|
|||
|
||||
setWorld: (@world) ->
|
||||
@worldLoaded = true
|
||||
@world.getFrame(Math.min(@getCurrentFrame(), @world.totalFrames - 1)).restoreState() unless @options.choosing
|
||||
lastFrame = Math.min(@getCurrentFrame(), @world.totalFrames - 1)
|
||||
@world.getFrame(lastFrame).restoreState() unless @options.choosing
|
||||
@spriteBoss.world = @world
|
||||
|
||||
@showLevel()
|
||||
|
@ -241,7 +242,7 @@ module.exports = Surface = class Surface extends CocoClass
|
|||
@onFrameChanged()
|
||||
|
||||
getCurrentFrame: ->
|
||||
return Math.max(0, Math.min(Math.floor(@currentFrame), @world.totalFrames - 1))
|
||||
return Math.max(0, Math.min(Math.floor(@currentFrame), @world.frames.length - 1))
|
||||
|
||||
getProgress: -> @currentFrame / @world.totalFrames
|
||||
|
||||
|
|
|
@ -186,7 +186,7 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
|
|||
error_saving: "Erro ao Guardar"
|
||||
saved: "Alterações Guardadas"
|
||||
password_mismatch: "As palavras-passe não coincidem."
|
||||
# password_repeat: "Please repeat your password."
|
||||
password_repeat: "Por favor repita a sua palavra-passe."
|
||||
job_profile: "Perfil de Emprego"
|
||||
job_profile_approved: "O seu perfil de emprego foi aprovado pelo CodeCombat. Os empregadores poderão vê-lo até que o defina como inativo ou não o tenha alterado à 4 semanas."
|
||||
job_profile_explanation: "Olá! Preencha isto e entraremos em contacto consigo sobre encontrar um emprego de desenvolvedor de software para si."
|
||||
|
@ -327,7 +327,7 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
|
|||
# pass_screen_blurb: "Review each candidate's code before reaching out. One employer found that 5x as many of our devs passed their technical screen than hiring from Hacker News."
|
||||
# make_hiring_easier: "Make my hiring easier, please."
|
||||
what: "O que é o CodeCombat?"
|
||||
what_blurb: "O CodeCombat é um jogo de programação, no navegador e multijogador. Os jogadores escrevem código para controlar as forças deles em batalha contra outros desenvolvedores. Nós suportamos JavaScript, Python, Lua, Clojure, CoffeeScript e Io."
|
||||
what_blurb: "O CodeCombat é um jogo de programação, no navegador e multijogador. Os jogadores escrevem código para controlar as forças deles em batalha contra outros desenvolvedores. Os nossos jogadores têm experiência com todos os conceitos tecnológicos principais."
|
||||
cost: "Quanto é que cobramos?"
|
||||
cost_blurb: "Cobramos 15% do salário do primeiro ano e ofereçemos uma garantia de devolução de 100% do dinheiro durante 90 dias. Não cobramos por candidatos que já estejam a ser ativamente entrevistados na sua companhia."
|
||||
candidate_name: "Nome"
|
||||
|
@ -503,13 +503,13 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
|
|||
pick_a_terrain: "Escolha Um Terreno"
|
||||
small: "Pequeno"
|
||||
grassy: "Com Relva"
|
||||
# fork_title: "Fork New Version"
|
||||
# fork_creating: "Creating Fork..."
|
||||
fork_title: "Bifurcar Nova Versão"
|
||||
fork_creating: "A Criar Bifurcação..."
|
||||
# randomize: "Randomize"
|
||||
# more: "More"
|
||||
# wiki: "Wiki"
|
||||
# live_chat: "Live Chat"
|
||||
level_some_options: "Algumas opções?"
|
||||
more: "Mais"
|
||||
wiki: "Wiki"
|
||||
live_chat: "Chat Ao Vivo"
|
||||
level_some_options: "Algumas Opções?"
|
||||
level_tab_thangs: "Thangs"
|
||||
level_tab_scripts: "Scripts"
|
||||
level_tab_settings: "Configurações"
|
||||
|
@ -522,19 +522,19 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
|
|||
# delete: "Delete"
|
||||
# duplicate: "Duplicate"
|
||||
level_settings_title: "Configurações"
|
||||
level_component_tab_title: "Componentes atuais"
|
||||
level_component_btn_new: "Cria um novo Componente"
|
||||
level_systems_tab_title: "Sistemas atuais"
|
||||
level_systems_btn_new: "Cria um novo Sistema"
|
||||
level_systems_btn_add: "Adiciona um Sistema"
|
||||
level_component_tab_title: "Componentes Atuais"
|
||||
level_component_btn_new: "Criar Novo Componente"
|
||||
level_systems_tab_title: "Sistemas Atuais"
|
||||
level_systems_btn_new: "Cria Novo Sistema"
|
||||
level_systems_btn_add: "Adicionar Sistema"
|
||||
level_components_title: "Voltar para Todos os Thangs"
|
||||
level_components_type: "Tipo"
|
||||
level_component_edit_title: "Editar Componente"
|
||||
# level_component_config_schema: "Config Schema"
|
||||
level_component_settings: "Configurações"
|
||||
level_system_edit_title: "Editar Sistema"
|
||||
create_system_title: "Criar novo Sistema"
|
||||
new_component_title: "Criar novo Componente"
|
||||
create_system_title: "Criar Novo Sistema"
|
||||
new_component_title: "Criar Novo Componente"
|
||||
new_component_field_system: "Sistema"
|
||||
new_article_title: "Criar um Novo Artigo"
|
||||
new_thang_title: "Criar um Novo Tipo de Thang"
|
||||
|
@ -559,20 +559,20 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
|
|||
name: "Nome"
|
||||
body: "Corpo"
|
||||
version: "Versão"
|
||||
commit_msg: "Mensagem de Commit"
|
||||
# version_history: "Version History"
|
||||
version_history_for: "Histórico de versões por: "
|
||||
commit_msg: "Enviar Mensagem"
|
||||
version_history: "Histórico de Versões"
|
||||
version_history_for: "Histórico de Versões para: "
|
||||
result: "Resultado"
|
||||
results: "Resultados"
|
||||
description: "Descrição"
|
||||
or: "ou"
|
||||
# subject: "Subject"
|
||||
subject: "Assunto"
|
||||
email: "E-mail"
|
||||
password: "Palavra-passe"
|
||||
message: "Mensagem"
|
||||
code: "Código"
|
||||
ladder: "Classificação"
|
||||
when: "quando"
|
||||
when: "Quando"
|
||||
opponent: "Adversário"
|
||||
rank: "Classificação"
|
||||
score: "Resultado"
|
||||
|
@ -768,17 +768,17 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
|
|||
ambassador_title_description: "(Suporte)"
|
||||
|
||||
ladder:
|
||||
please_login: "Por favor, faz log in antes de jogar um jogo para o campeonato."
|
||||
my_matches: "Os meus jogos"
|
||||
please_login: "Por favor inicie sessão antes de jogar um jogo do campeonato."
|
||||
my_matches: "Os Meus Jogos"
|
||||
simulate: "Simular"
|
||||
simulation_explanation: "Simulando jogos podes fazer com que o teu jogo seja classificado mais rapidamente!"
|
||||
simulation_explanation: "Ao simular jogos pode ter o seu jogo classificado mais rapidamente!"
|
||||
simulate_games: "Simular Jogos!"
|
||||
# simulate_all: "RESET AND SIMULATE GAMES"
|
||||
# games_simulated_by: "Games simulated by you:"
|
||||
# games_simulated_for: "Games simulated for you:"
|
||||
# games_simulated: "Games simulated"
|
||||
# games_played: "Games played"
|
||||
# ratio: "Ratio"
|
||||
games_simulated_by: "Jogos simulados por si:"
|
||||
games_simulated_for: "Jogos simulados para si:"
|
||||
games_simulated: "Jogos simulados"
|
||||
games_played: "Jogos jogados"
|
||||
ratio: "Rácio"
|
||||
leaderboard: "Tabela de Classificação"
|
||||
battle_as: "Lutar como "
|
||||
summary_your: "As tuas "
|
||||
|
@ -868,29 +868,29 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
|
|||
# server_error: "Server error."
|
||||
# unknown: "Unknown error."
|
||||
|
||||
# resources:
|
||||
# your_sessions: "Your Sessions"
|
||||
# level: "Level"
|
||||
resources:
|
||||
your_sessions: "As Suas Sessões"
|
||||
level: "Nível"
|
||||
# social_network_apis: "Social Network APIs"
|
||||
# facebook_status: "Facebook Status"
|
||||
# facebook_friends: "Facebook Friends"
|
||||
facebook_friends: "Amigos do Facebook"
|
||||
# facebook_friend_sessions: "Facebook Friend Sessions"
|
||||
# gplus_friends: "G+ Friends"
|
||||
gplus_friends: "Amigos do Google+"
|
||||
# gplus_friend_sessions: "G+ Friend Sessions"
|
||||
# leaderboard: "Leaderboard"
|
||||
leaderboard: "Tabela de Classificação"
|
||||
# user_schema: "User Schema"
|
||||
# user_profile: "User Profile"
|
||||
# patches: "Patches"
|
||||
# patched_model: "Source Document"
|
||||
# model: "Model"
|
||||
# system: "System"
|
||||
# component: "Component"
|
||||
# components: "Components"
|
||||
# thang: "Thang"
|
||||
# thangs: "Thangs"
|
||||
# level_session: "Your Session"
|
||||
# opponent_session: "Opponent Session"
|
||||
# article: "Article"
|
||||
system: "Sistema"
|
||||
component: "Componente"
|
||||
components: "Componentes"
|
||||
thang: "Thang"
|
||||
thangs: "Thangs"
|
||||
level_session: "A Sua Sessão"
|
||||
opponent_session: "Sessão Do Oponente"
|
||||
article: "Artigo"
|
||||
# user_names: "User Names"
|
||||
# thang_names: "Thang Names"
|
||||
# files: "Files"
|
||||
|
@ -900,7 +900,7 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
|
|||
# sprite_sheet: "Sprite Sheet"
|
||||
# candidate_sessions: "Candidate Sessions"
|
||||
# user_remark: "User Remark"
|
||||
# versions: "Versions"
|
||||
versions: "Versões"
|
||||
|
||||
# delta:
|
||||
# added: "Added"
|
||||
|
|
74
app/styles/admin/candidates.sass
Normal file
74
app/styles/admin/candidates.sass
Normal file
|
@ -0,0 +1,74 @@
|
|||
#admin-candidates-view
|
||||
|
||||
h1, h2, h3
|
||||
font: Arial
|
||||
|
||||
.see-candidates-header
|
||||
margin: 30px
|
||||
text-align: center
|
||||
|
||||
#see-candidates
|
||||
cursor: pointer
|
||||
|
||||
.employer_icon
|
||||
width: 125px
|
||||
float: left
|
||||
margin: 0px 15px 15px 0px
|
||||
|
||||
.information_row
|
||||
height: 150px
|
||||
padding-right: 15px
|
||||
|
||||
#leftside
|
||||
width: 500px
|
||||
float: left
|
||||
|
||||
#rightside
|
||||
width: 500px
|
||||
float: left
|
||||
|
||||
.tablesorter
|
||||
//img
|
||||
// display: none
|
||||
|
||||
.tablesorter-header
|
||||
cursor: pointer
|
||||
&:hover
|
||||
color: black
|
||||
|
||||
&:first-child
|
||||
// Make sure that "Developer #56" doesn't wrap onto second row
|
||||
min-width: 110px
|
||||
|
||||
.tablesorter-headerAsc
|
||||
background-color: #cfc
|
||||
|
||||
.tablesorter-headerDesc
|
||||
background-color: #ccf
|
||||
|
||||
tr
|
||||
cursor: pointer
|
||||
|
||||
tr.expired
|
||||
opacity: 0.5
|
||||
|
||||
code
|
||||
background-color: rgb(220, 220, 220)
|
||||
color: #555
|
||||
margin: 2px 0
|
||||
display: inline-block
|
||||
text-transform: lowercase
|
||||
|
||||
td:nth-child(3) select
|
||||
min-width: 100px
|
||||
td:nth-child(6) select
|
||||
min-width: 50px
|
||||
td:nth-child(7) select
|
||||
min-width: 100px
|
||||
|
||||
#employers-view, #profile-view.viewed-by-employer
|
||||
#outer-content-wrapper, #intermediate-content-wrapper, #inner-content-wrapper
|
||||
background: #949494
|
||||
|
||||
.main-content-area
|
||||
background-color: #EAEAEA
|
|
@ -25,6 +25,8 @@ block content
|
|||
a(href="/admin/level_sessions", data-i18n="admin.av_entities_active_instances_url") Active Instances
|
||||
li
|
||||
a(href="/admin/employer_list", data-i18n="admin.av_entities_employer_list_url") Employer List
|
||||
li
|
||||
a(href="/admin/candidates") Candidate List
|
||||
|
||||
h4(data-i18n="admin.av_other_sub_title") Other
|
||||
|
||||
|
|
110
app/templates/admin/candidates.jade
Normal file
110
app/templates/admin/candidates.jade
Normal file
|
@ -0,0 +1,110 @@
|
|||
extends /templates/base
|
||||
block content
|
||||
if !me.isAdmin()
|
||||
h1 Admins Only
|
||||
if me.isAdmin()
|
||||
h1(data-i18n="employers.want_to_hire_our_players") Hire CodeCombat Players
|
||||
if !isEmployer && !me.isAdmin()
|
||||
div#info_wrapper
|
||||
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
|
||||
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 ✗
|
|
@ -26,8 +26,8 @@ block content
|
|||
li(id="#{component.get('name')}#{doc.name}")
|
||||
| #{doc.name}
|
||||
ul.specialList
|
||||
if doc.description[language.substring(1,language.length-1)]
|
||||
li!=marked(doc.description[language.substring(1,language.length-1)])
|
||||
if doc.description[language]
|
||||
li!=marked(doc.description[language])
|
||||
else
|
||||
li!=marked(doc.description)
|
||||
|
||||
|
|
207
app/views/admin/candidates_view.coffee
Normal file
207
app/views/admin/candidates_view.coffee
Normal file
|
@ -0,0 +1,207 @@
|
|||
View = require 'views/kinds/RootView'
|
||||
template = require 'templates/admin/candidates'
|
||||
app = require 'application'
|
||||
User = require 'models/User'
|
||||
UserRemark = require 'models/UserRemark'
|
||||
{me} = require 'lib/auth'
|
||||
CocoCollection = require 'collections/CocoCollection'
|
||||
EmployerSignupView = require 'views/modal/employer_signup_modal'
|
||||
|
||||
class CandidatesCollection extends CocoCollection
|
||||
url: '/db/user/x/candidates'
|
||||
model: User
|
||||
|
||||
class UserRemarksCollection extends CocoCollection
|
||||
url: '/db/user.remark?project=contact,contactName,user'
|
||||
model: UserRemark
|
||||
|
||||
module.exports = class EmployersView extends View
|
||||
id: "admin-candidates-view"
|
||||
template: template
|
||||
|
||||
events:
|
||||
'click tbody tr': 'onCandidateClicked'
|
||||
|
||||
constructor: (options) ->
|
||||
super options
|
||||
@getCandidates()
|
||||
|
||||
afterRender: ->
|
||||
super()
|
||||
@sortTable() if @candidates.models.length
|
||||
|
||||
afterInsert: ->
|
||||
super()
|
||||
_.delay @checkForEmployerSignupHash, 500
|
||||
|
||||
getRenderData: ->
|
||||
ctx = super()
|
||||
ctx.isEmployer = @isEmployer()
|
||||
ctx.candidates = _.sortBy @candidates.models, (c) -> c.get('jobProfile').updated
|
||||
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')
|
||||
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
|
||||
|
||||
isEmployer: ->
|
||||
userPermissions = me.get('permissions') ? []
|
||||
_.contains userPermissions, "employer"
|
||||
|
||||
getCandidates: ->
|
||||
@candidates = new CandidatesCollection()
|
||||
@candidates.fetch()
|
||||
@remarks = new UserRemarksCollection()
|
||||
@remarks.fetch()
|
||||
# Re-render when we have fetched them, but don't wait and show a progress bar while loading.
|
||||
@listenToOnce @candidates, 'all', @renderCandidatesAndSetupScrolling
|
||||
@listenToOnce @remarks, 'all', @renderCandidatesAndSetupScrolling
|
||||
|
||||
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)})
|
||||
|
||||
checkForEmployerSignupHash: =>
|
||||
if window.location.hash is "#employerSignupLoggingIn" and not ("employer" in me.get("permissions"))
|
||||
@openModalView application.router.getView("modal/employer_signup","_modal")
|
||||
window.location.hash = ""
|
||||
|
||||
sortTable: ->
|
||||
# http://mottie.github.io/tablesorter/docs/example-widget-bootstrap-theme.html
|
||||
$.extend $.tablesorter.themes.bootstrap,
|
||||
# these classes are added to the table. To see other table classes available,
|
||||
# look here: http://twitter.github.com/bootstrap/base-css.html#tables
|
||||
table: "table table-bordered"
|
||||
caption: "caption"
|
||||
header: "bootstrap-header" # give the header a gradient background
|
||||
footerRow: ""
|
||||
footerCells: ""
|
||||
icons: "" # add "icon-white" to make them white; this icon class is added to the <i> in the header
|
||||
sortNone: "bootstrap-icon-unsorted"
|
||||
sortAsc: "icon-chevron-up" # glyphicon glyphicon-chevron-up" # we are still using v2 icons
|
||||
sortDesc: "icon-chevron-down" # glyphicon-chevron-down" # we are still using v2 icons
|
||||
active: "" # applied when column is sorted
|
||||
hover: "" # use custom css here - bootstrap class may not override it
|
||||
filterRow: "" # filter row class
|
||||
even: "" # odd row zebra striping
|
||||
odd: "" # even row zebra striping
|
||||
|
||||
|
||||
# e = exact text from cell
|
||||
# n = normalized value returned by the column parser
|
||||
# f = search filter input value
|
||||
# i = column index
|
||||
# $r = ???
|
||||
filterSelectExactMatch = (e, n, f, i, $r) -> e is f
|
||||
|
||||
# call the tablesorter plugin and apply the uitheme widget
|
||||
@$el.find(".tablesorter").tablesorter
|
||||
theme: "bootstrap"
|
||||
widthFixed: true
|
||||
headerTemplate: "{content} {icon}"
|
||||
textSorter:
|
||||
6: (a, b, direction, column, table) ->
|
||||
days = []
|
||||
for s in [a, b]
|
||||
n = parseInt s
|
||||
n = 0 unless _.isNumber n
|
||||
n = 1 if /^a/.test s
|
||||
for [duration, factor] in [
|
||||
[/second/i, 1 / (86400 * 1000)]
|
||||
[/minute/i, 1 / 1440]
|
||||
[/hour/i, 1 / 24]
|
||||
[/week/i, 7]
|
||||
[/month/i, 30.42]
|
||||
[/year/i, 365.2425]
|
||||
]
|
||||
if duration.test s
|
||||
n *= factor
|
||||
break
|
||||
if /^in /i.test s
|
||||
n *= -1
|
||||
days.push n
|
||||
days[0] - days[1]
|
||||
sortList: if @isEmployer() or me.isAdmin() then [[6, 0]] else [[0, 1]]
|
||||
# widget code contained in the jquery.tablesorter.widgets.js file
|
||||
# use the zebra stripe widget if you plan on hiding any rows (filter widget)
|
||||
widgets: ["uitheme", "zebra", "filter"]
|
||||
widgetOptions:
|
||||
# using the default zebra striping class name, so it actually isn't included in the theme variable above
|
||||
# this is ONLY needed for bootstrap theming if you are using the filter widget, because rows are hidden
|
||||
zebra: ["even", "odd"]
|
||||
|
||||
# extra css class applied to the table row containing the filters & the inputs within that row
|
||||
filter_cssFilter: ""
|
||||
|
||||
# If there are child rows in the table (rows with class name from "cssChildRow" option)
|
||||
# and this option is true and a match is found anywhere in the child row, then it will make that row
|
||||
# visible; default is false
|
||||
filter_childRows: false
|
||||
|
||||
# if true, filters are collapsed initially, but can be revealed by hovering over the grey bar immediately
|
||||
# below the header row. Additionally, tabbing through the document will open the filter row when an input gets focus
|
||||
filter_hideFilters: false
|
||||
|
||||
# Set this option to false to make the searches case sensitive
|
||||
filter_ignoreCase: true
|
||||
|
||||
# jQuery selector string of an element used to reset the filters
|
||||
filter_reset: ".reset"
|
||||
|
||||
# Use the $.tablesorter.storage utility to save the most recent filters
|
||||
filter_saveFilters: true
|
||||
|
||||
# Delay in milliseconds before the filter widget starts searching; This option prevents searching for
|
||||
# every character while typing and should make searching large tables faster.
|
||||
filter_searchDelay: 150
|
||||
|
||||
# Set this option to true to use the filter to find text from the start of the column
|
||||
# So typing in "a" will find "albert" but not "frank", both have a's; default is false
|
||||
filter_startsWith: false
|
||||
|
||||
filter_functions:
|
||||
2:
|
||||
"Full-time": filterSelectExactMatch
|
||||
"Part-time": filterSelectExactMatch
|
||||
"Contracting": filterSelectExactMatch
|
||||
"Remote": filterSelectExactMatch
|
||||
"Internship": filterSelectExactMatch
|
||||
5:
|
||||
"0-1": (e, n, f, i, $r) -> n <= 1
|
||||
"2-5": (e, n, f, i, $r) -> 2 <= n <= 5
|
||||
"6+": (e, n, f, i, $r) -> 6 <= n
|
||||
6:
|
||||
"Last day": (e, n, f, i, $r) ->
|
||||
days = parseFloat $($r.find('td')[i]).data('profile-age')
|
||||
days <= 1
|
||||
"Last week": (e, n, f, i, $r) ->
|
||||
days = parseFloat $($r.find('td')[i]).data('profile-age')
|
||||
days <= 7
|
||||
"Last 4 weeks": (e, n, f, i, $r) ->
|
||||
days = parseFloat $($r.find('td')[i]).data('profile-age')
|
||||
days <= 28
|
||||
8:
|
||||
"✓": filterSelectExactMatch
|
||||
"✗": filterSelectExactMatch
|
||||
|
||||
onCandidateClicked: (e) ->
|
||||
id = $(e.target).closest('tr').data('candidate-id')
|
||||
if id
|
||||
if window.history
|
||||
oldState = _.cloneDeep window.history.state ? {}
|
||||
oldState["lastViewedCandidateID"] = id
|
||||
window.history.replaceState(oldState,"")
|
||||
else
|
||||
window.location.hash = id
|
||||
url = "/account/profile/#{id}"
|
||||
window.open url,"_blank"
|
||||
else
|
||||
@openModalView new EmployerSignupView
|
|
@ -34,5 +34,5 @@ module.exports = class UnnamedView extends RootView
|
|||
if (me.get('aceConfig')?.language?) is false
|
||||
c.language = 'javascript'
|
||||
else
|
||||
c.language = JSON.stringify(me.get('aceConfig').language)
|
||||
c.language = me.get('aceConfig').language
|
||||
c
|
||||
|
|
|
@ -40,7 +40,7 @@ module.exports = class EditorLevelView extends View
|
|||
constructor: (options, @levelID) ->
|
||||
super options
|
||||
@supermodel.shouldSaveBackups = (model) ->
|
||||
model.constructor.className in ['Level', 'LevelComponent', 'LevelSystem']
|
||||
model.constructor.className in ['Level', 'LevelComponent', 'LevelSystem', 'ThangType']
|
||||
@levelLoader = new LevelLoader supermodel: @supermodel, levelID: @levelID, headless: true, editorMode: true
|
||||
@level = @levelLoader.level
|
||||
@files = new DocumentFiles(@levelLoader.level)
|
||||
|
|
|
@ -84,8 +84,9 @@ module.exports = class PlayLevelView extends View
|
|||
@saveScreenshot = _.throttle @saveScreenshot, 30000
|
||||
|
||||
if @isEditorPreview
|
||||
# wait to see if it's just given to us through setLevel
|
||||
f = => @load() unless @levelLoader
|
||||
@supermodel.shouldSaveBackups = (model) -> # Make sure to load possibly changed things from localStorage.
|
||||
model.constructor.className in ['Level', 'LevelComponent', 'LevelSystem', 'ThangType']
|
||||
f = => @load() unless @levelLoader # Wait to see if it's just given to us through setLevel.
|
||||
setTimeout f, 100
|
||||
else
|
||||
@load()
|
||||
|
|
|
@ -117,7 +117,7 @@ module.exports = class PlayView extends View
|
|||
difficulty: 2
|
||||
id: 'emphasis-on-aim'
|
||||
image: '/file/db/level/525f384d96cd77000000000f/munchkin_masher_icon.png'
|
||||
description: 'Chose your targets carefully.'
|
||||
description: 'Choose your targets carefully.'
|
||||
}
|
||||
{
|
||||
name: 'Zone of Danger'
|
||||
|
|
|
@ -8,15 +8,15 @@ LevelSession = require '../levels/sessions/LevelSession'
|
|||
Level = require '../levels/Level'
|
||||
log = require 'winston'
|
||||
sendwithus = require '../sendwithus'
|
||||
if config.isProduction
|
||||
if config.isProduction and config.redis.host isnt 'localhost'
|
||||
lockManager = require '../commons/LockManager'
|
||||
|
||||
|
||||
module.exports.setup = (app) ->
|
||||
app.all config.mail.mailchimpWebhook, handleMailchimpWebHook
|
||||
app.get '/mail/cron/ladder-update', handleLadderUpdate
|
||||
if lockManager
|
||||
setupScheduledEmails()
|
||||
|
||||
|
||||
setupScheduledEmails = ->
|
||||
testForLockManager()
|
||||
mailTasks = [
|
||||
|
@ -31,15 +31,15 @@ setupScheduledEmails = ->
|
|||
]
|
||||
|
||||
for mailTask in mailTasks
|
||||
setInterval mailTask.taskFunction, mailTask.frequencyMs
|
||||
|
||||
setInterval mailTask.taskFunction, mailTask.frequencyMs
|
||||
|
||||
testForLockManager = -> unless lockManager then throw "The system isn't configured to do distributed locking!"
|
||||
|
||||
|
||||
### Candidate Update Reminder Task ###
|
||||
|
||||
candidateUpdateProfileTask = ->
|
||||
mailTaskName = "candidateUpdateProfileTask"
|
||||
lockDurationMs = 2 * 60 * 1000
|
||||
lockDurationMs = 2 * 60 * 1000
|
||||
currentDate = new Date()
|
||||
timeRanges = []
|
||||
for weekPair in [[4, 2,'two weeks'], [8, 4, 'four weeks'], [52, 8, 'eight weeks']]
|
||||
|
@ -66,12 +66,12 @@ emailTimeRange = (timeRange, emailTimeRangeCallback) ->
|
|||
"mailTaskName": @mailTaskName
|
||||
async.waterfall [
|
||||
findAllCandidatesWithinTimeRange.bind(waterfallContext)
|
||||
(unfilteredCandidates, cb) ->
|
||||
(unfilteredCandidates, cb) ->
|
||||
async.reject unfilteredCandidates, candidateFilter.bind(waterfallContext), cb.bind(null, null)
|
||||
(filteredCandidates, cb) ->
|
||||
async.each filteredCandidates, sendReminderEmailToCandidate.bind(waterfallContext), cb
|
||||
], emailTimeRangeCallback
|
||||
|
||||
|
||||
findAllCandidatesWithinTimeRange = (cb) ->
|
||||
findParameters =
|
||||
"jobProfile.updated":
|
||||
|
@ -80,7 +80,7 @@ findAllCandidatesWithinTimeRange = (cb) ->
|
|||
"jobProfileApproved": true
|
||||
selection = "_id email jobProfile.name jobProfile.updated emails" #make sure to check for anyNotes too.
|
||||
User.find(findParameters).select(selection).lean().exec cb
|
||||
|
||||
|
||||
candidateFilter = (candidate, sentEmailFilterCallback) ->
|
||||
if candidate.emails?.anyNotes?.enabled is false or candidate.emails?.recruitNotes?.enabled is false
|
||||
return sentEmailFilterCallback true
|
||||
|
@ -102,7 +102,7 @@ findEmployersSignedUpAfterDate = (dateObject, cb) ->
|
|||
employerAt: {$exists: true}
|
||||
permissions: "employer"
|
||||
User.count countParameters, cb
|
||||
|
||||
|
||||
sendReminderEmailToCandidate = (candidate, sendEmailCallback) ->
|
||||
findEmployersSignedUpAfterDate new Date(candidate.jobProfile.updated), (err, employersAfterCount) =>
|
||||
if err?
|
||||
|
@ -136,7 +136,7 @@ sendReminderEmailToCandidate = (candidate, sendEmailCallback) ->
|
|||
### Internal Candidate Update Reminder Email ###
|
||||
internalCandidateUpdateTask = ->
|
||||
mailTaskName = "internalCandidateUpdateTask"
|
||||
lockDurationMs = 2 * 60 * 1000
|
||||
lockDurationMs = 2 * 60 * 1000
|
||||
lockManager.setLock mailTaskName, lockDurationMs, (err) ->
|
||||
if err? then return log.error "Error getting a distributed lock for task #{mailTaskName}: #{err}"
|
||||
emailInternalCandidateUpdateReminder.call {"mailTaskName":mailTaskName}, (err) ->
|
||||
|
@ -162,31 +162,31 @@ emailInternalCandidateUpdateReminder = (internalCandidateUpdateReminderCallback)
|
|||
(filteredCandidates, cb) ->
|
||||
async.each filteredCandidates, sendInternalCandidateUpdateReminder.bind(asyncContext), cb
|
||||
], internalCandidateUpdateReminderCallback
|
||||
|
||||
|
||||
findNonApprovedCandidatesWhoUpdatedJobProfileToday = (cb) ->
|
||||
findParameters =
|
||||
findParameters =
|
||||
"jobProfile.updated":
|
||||
$lte: @currentTime.toISOString()
|
||||
$gt: @beginningOfUTCDay.toISOString()
|
||||
"jobProfileApproved": false
|
||||
"jobProfileApproved": false
|
||||
User.find(findParameters).select("_id jobProfile.name jobProfile.updated").lean().exec cb
|
||||
|
||||
|
||||
candidatesUpdatedTodayFilter = (candidate, cb) ->
|
||||
findParameters =
|
||||
"user": candidate._id
|
||||
"mailTask": @mailTaskName
|
||||
"metadata.beginningOfUTCDay": @beginningOfUTCDay
|
||||
"metadata.beginningOfUTCDay": @beginningOfUTCDay
|
||||
MailSent.find(findParameters).lean().exec (err, sentMail) ->
|
||||
if err?
|
||||
log.error "Error finding mail sent for task #{@mailTaskName} and user #{candidate._id}!"
|
||||
cb true
|
||||
else
|
||||
cb Boolean(sentMail.length)
|
||||
|
||||
|
||||
sendInternalCandidateUpdateReminder = (candidate, cb) ->
|
||||
context =
|
||||
email_id: "tem_Ac7nhgKqatTHBCgDgjF5pE"
|
||||
recipient:
|
||||
recipient:
|
||||
address: "team@codecombat.com"
|
||||
name: "The CodeCombat Team"
|
||||
email_data:
|
||||
|
@ -197,7 +197,7 @@ sendInternalCandidateUpdateReminder = (candidate, cb) ->
|
|||
user: candidate._id
|
||||
metadata:
|
||||
beginningOfUTCDay: @beginningOfUTCDay
|
||||
|
||||
|
||||
MailSent.create newSentMail, (err) ->
|
||||
if err? then return cb err
|
||||
sendwithus.api.send context, (err, result) ->
|
||||
|
@ -208,7 +208,7 @@ sendInternalCandidateUpdateReminder = (candidate, cb) ->
|
|||
### Employer New Candidates Available Email ###
|
||||
employerNewCandidatesAvailableTask = ->
|
||||
mailTaskName = "employerNewCandidatesAvailableTask"
|
||||
lockDurationMs = 2 * 60 * 1000
|
||||
lockDurationMs = 2 * 60 * 1000
|
||||
lockManager.setLock mailTaskName, lockDurationMs, (err) ->
|
||||
if err? then return log.error "Error getting a distributed lock for task #{mailTaskName}: #{err}"
|
||||
emailEmployerNewCandidatesAvailable.call {"mailTaskName":mailTaskName}, (err) ->
|
||||
|
@ -221,10 +221,10 @@ employerNewCandidatesAvailableTask = ->
|
|||
|
||||
emailEmployerNewCandidatesAvailable = (emailEmployerNewCandidatesAvailableCallback) ->
|
||||
currentTime = new Date()
|
||||
asyncContext =
|
||||
asyncContext =
|
||||
"currentTime": currentTime
|
||||
"mailTaskName": @mailTaskName
|
||||
|
||||
|
||||
async.waterfall [
|
||||
findAllEmployers
|
||||
makeEmployerNamesEasilyAccessible
|
||||
|
@ -233,15 +233,15 @@ emailEmployerNewCandidatesAvailable = (emailEmployerNewCandidatesAvailableCallba
|
|||
(employersToEmail, cb) ->
|
||||
async.each employersToEmail, sendEmployerNewCandidatesAvailableEmail.bind(asyncContext), cb
|
||||
], emailEmployerNewCandidatesAvailableCallback
|
||||
|
||||
|
||||
findAllEmployers = (cb) ->
|
||||
findParameters =
|
||||
findParameters =
|
||||
"employerAt":
|
||||
$exists: true
|
||||
$exists: true
|
||||
permissions: "employer"
|
||||
selection = "_id email employerAt signedEmployerAgreement.data.firstName signedEmployerAgreement.data.lastName activity dateCreated emails"
|
||||
User.find(findParameters).select(selection).lean().exec cb
|
||||
|
||||
|
||||
makeEmployerNamesEasilyAccessible = (allEmployers, cb) ->
|
||||
for employer, index in allEmployers
|
||||
if employer.signedEmployerAgreement?.data?.firstName
|
||||
|
@ -249,15 +249,17 @@ makeEmployerNamesEasilyAccessible = (allEmployers, cb) ->
|
|||
delete employer.signedEmployerAgreement
|
||||
allEmployers[index] = employer
|
||||
cb null, allEmployers
|
||||
|
||||
|
||||
employersEmailedDigestMoreThanWeekAgoFilter = (employer, cb) ->
|
||||
if employer.emails?.employerNotes?.enabled is false
|
||||
return cb true
|
||||
findParameters =
|
||||
if not employer.signedEmployerAgreement and not employer.activity?.login?
|
||||
return cb true
|
||||
findParameters =
|
||||
"user": employer._id
|
||||
"mailTask": @mailTaskName
|
||||
"sent":
|
||||
$gt: new Date(@currentTime.getTime() - 7 * 24 * 60 * 60 * 1000)
|
||||
$gt: new Date(@currentTime.getTime() - 7 * 24 * 60 * 60 * 1000)
|
||||
MailSent.find(findParameters).lean().exec (err, sentMail) ->
|
||||
if err?
|
||||
log.error "Error finding mail sent for task #{@mailTaskName} and employer #employer._id}!"
|
||||
|
@ -266,17 +268,17 @@ employersEmailedDigestMoreThanWeekAgoFilter = (employer, cb) ->
|
|||
cb Boolean(sentMail.length)
|
||||
|
||||
sendEmployerNewCandidatesAvailableEmail = (employer, cb) ->
|
||||
lastLoginDate = employer.activity?.login?.last ? employer.dateCreated
|
||||
lastLoginDate = employer.activity?.login?.last ? employer.dateCreated
|
||||
countParameters =
|
||||
"jobProfileApproved": true
|
||||
$or: [
|
||||
jobProfileApprovedDate:
|
||||
jobProfileApprovedDate:
|
||||
$gt: lastLoginDate.toISOString()
|
||||
,
|
||||
jobProfileApprovedDate:
|
||||
$exists: false
|
||||
"jobProfile.updated":
|
||||
$gt: lastLoginDate.toISOString()
|
||||
$gt: lastLoginDate.toISOString()
|
||||
]
|
||||
User.count countParameters, (err, numberOfCandidatesSinceLogin) =>
|
||||
if err? then return cb err
|
||||
|
@ -313,8 +315,8 @@ newRecruitLeaderboardEmailTask = ->
|
|||
lockManager.setLock mailTaskName, lockDurationMs, (err, lockResult) ->
|
||||
###
|
||||
### End New Recruit Leaderboard Email ###
|
||||
|
||||
### Employer Matching Candidate Notification Email ###
|
||||
|
||||
### Employer Matching Candidate Notification Email ###
|
||||
###
|
||||
employerMatchingCandidateNotificationTask = ->
|
||||
# tem_mYsepTfWQ265noKfZJcbBH
|
||||
|
@ -325,7 +327,7 @@ employerMatchingCandidateNotificationTask = ->
|
|||
###
|
||||
### End Employer Matching Candidate Notification Email ###
|
||||
### Ladder Update Email ###
|
||||
|
||||
### Employer ignore ###
|
||||
DEBUGGING = false
|
||||
LADDER_PREGAME_INTERVAL = 2 * 3600 * 1000 # Send emails two hours before players last submitted.
|
||||
getTimeFromDaysAgo = (now, daysAgo) ->
|
||||
|
@ -342,7 +344,7 @@ isRequestFromDesignatedCronHandler = (req, res) ->
|
|||
return true
|
||||
|
||||
|
||||
|
||||
|
||||
handleLadderUpdate = (req, res) ->
|
||||
log.info('Going to see about sending ladder update emails.')
|
||||
requestIsFromDesignatedCronHandler = isRequestFromDesignatedCronHandler req, res
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue