Merge branch 'master' into production

This commit is contained in:
Nick Winter 2015-11-12 10:25:51 -08:00
commit 44fb30b81e
27 changed files with 63 additions and 88 deletions

View file

@ -4,7 +4,7 @@ module.exports.createContiguousDays = (timeframeDays, skipToday=true) ->
# Return list of last 'timeframeDays' contiguous days in yyyy-mm-dd format
days = []
currentDate = new Date()
currentDate.setUTCDate(currentDate.getUTCDate() - timeframeDays)
currentDate.setUTCDate(currentDate.getUTCDate() - timeframeDays + 1)
currentDate.setUTCDate(currentDate.getUTCDate() - 1) if skipToday
for i in [0...timeframeDays]
currentDay = currentDate.toISOString().substr(0, 10)

View file

@ -34,6 +34,7 @@ module.exports = class LevelLoader extends CocoClass
@sessionless = options.sessionless
@spectateMode = options.spectateMode ? false
@observing = options.observing
@isCourse = options.courseID?
@worldNecessities = []
@listenTo @supermodel, 'resource-loaded', @onWorldNecessityLoaded
@ -56,6 +57,12 @@ module.exports = class LevelLoader extends CocoClass
@listenToOnce @level, 'sync', @onLevelLoaded
onLevelLoaded: ->
if @isCourse and @level.get('type', true) not in ['course', 'course-ladder']
# Because we now use original hero levels for both hero and course levels, we fake being a course level in this context.
originalGet = @level.get
@level.get = ->
return 'course' if arguments[0] is 'type'
originalGet.apply @, arguments
@loadSession() unless @sessionless
@populateLevel()

View file

@ -1,12 +1,15 @@
c = require './../schemas'
CampaignSchema = c.object()
CampaignSchema = c.object
default:
type: 'hero'
c.extendNamedProperties CampaignSchema # name first
_.extend CampaignSchema.properties, {
i18n: {type: 'object', title: 'i18n', format: 'i18n', props: ['name', 'fullName', 'description']}
fullName: { type: 'string', title: 'Full Name', description: 'Ex.: "Kithgard Dungeon"' }
description: { type: 'string', format: 'string', description: 'How long it takes and what players learn.' }
type: c.shortString(title: 'Type', description: 'What kind of campaign this is.', 'enum': ['hero', 'course'])
ambientSound: c.object {},
mp3: { type: 'string', format: 'sound-file' }

View file

@ -312,11 +312,11 @@ _.extend UserSchema.properties,
siteref: { type: 'string' }
referrer: { type: 'string' }
chinaVersion: { type: 'boolean' } # Old
chinaVersion: { type: 'boolean' } # Old, can be removed after we make sure it's deleted from all users
country: { type: 'string', enum: ['brazil', 'china'] } # New, supports multiple countries for different versions--only set for specific countries where we have premium servers right now
clans: c.array {}, c.objectId()
currentCourse: c.object {}, {
currentCourse: c.object {}, { # Old, can be removed after we deploy and delete it from all users
courseID: c.objectId({})
courseInstanceID: c.objectId({})
}

View file

@ -58,6 +58,12 @@ block content
span San Francisco, CA 94107
br
a(href='mailto:team@codecombat.com') team@codecombat.com
if me.get('preferredLanguage') == 'pt-BR'
span CodeCombat, Brazil
br
span Rua 1814 Militão Chaves
br
span Natal, Brazil, RN 59064-500
ul.col-sm-6.team-column

View file

@ -2,7 +2,7 @@ extends /templates/core/modal-base
block modal-header-content
h3 Applicant Code for
span.spl= session.get('levelName')
span.spl= view.session.get('levelName')
block modal-body-content
.level-session-code-view

View file

@ -7,10 +7,10 @@ block content
.center
img(src="/images/pages/careers/recruiting.png")
if position === 'software-engineer'
if view.position === 'software-engineer'
h1.center Software Engineer
+company-blurb
.big-side-margins
h3 Engineering at CodeCombat
@ -24,7 +24,7 @@ block content
h3 Upcoming Projects
.big-text
p
p
strong iPad app
div Bring the CodeCombat experience to the iPad
@ -46,7 +46,7 @@ block content
+next-steps
else if position === 'software-engineer-ios'
else if view.position === 'software-engineer-ios'
h1.center Software Engineer, iOS
+company-blurb
@ -64,7 +64,7 @@ block content
+next-steps
else if position === 'game-designer'
else if view.position === 'game-designer'
h1.center Game Designer
+company-blurb
@ -109,7 +109,7 @@ block content
a(href="/careers/game-designer") Game Designer
br
p Don't see a position that suits you, but still want to to contribute?
p
p
span.spr Please contact us at
a(href="mailto:careers@codecombat.com") careers@codecombat.com

View file

@ -4,14 +4,14 @@ block modal-header-content
h3 Save Changes to Campaign
block modal-body-content
if !modelsToSave.models.length
if !view.modelsToSave.models.length
.alert.alert-info(data-i18n="delta.no_changes") No changes
for model in modelsToSave.models
for model in view.modelsToSave.models
.panel.panel-default
.panel-heading
span.panel-title.spr= model.get('name')
span.text-muted= model.constructor.className
span.text-muted= model.constructor.className
.panel-body
.delta-view(data-model-id=model.id)

View file

@ -1,7 +1,7 @@
extends /templates/core/modal-base
block modal-header-content
if nonUserCodeProblem
if view.options.nonUserCodeProblem
h3(data-i18n="play_level.non_user_code_problem_title") Unable to Load Level
else
h3(data-i18n="play_level.infinite_loop_title") Infinite Loop Detected

View file

@ -1,3 +1,3 @@
h4
span(data-i18n="play_level.tome_select_a_thang") Select Someone for
code #{spell.name}(#{(spell.parameters || []).join(", ")})
span.spr(data-i18n="play_level.tome_select_a_thang") Select Someone for
code #{view.spell.name}(#{(view.spell.parameters || []).join(", ")})

View file

@ -7,8 +7,3 @@ module.exports = class CareersView extends RootView
constructor: (options, @position) ->
super options
getRenderData: ->
context = super()
context.position = @position
context

View file

@ -33,6 +33,7 @@ module.exports = class ContactModal extends ModalView
res = tv4.validateMultiple contactMessage, contactSchema
return forms.applyErrorsToForm @$el, res.errors unless res.valid
@populateBrowserData contactMessage
contactMessage.country = me.get('country')
window.tracker?.trackEvent 'Sent Feedback', message: contactMessage
sendContactMessage contactMessage, @$el
$.post "/db/user/#{me.id}/track/contact_codecombat"

View file

@ -85,7 +85,7 @@ module.exports = class CourseDetailsView extends RootView
# console.log 'onCampaignSync'
if @courseInstanceID
@loadCourseInstance(@courseInstanceID)
else if !me.isAnonymous()
else unless me.isAnonymous()
@courseInstances = new CocoCollection([], { url: "/db/user/#{me.id}/course_instances", model: CourseInstance})
@listenToOnce @courseInstances, 'sync', @onCourseInstancesSync
@supermodel.loadCollection(@courseInstances, 'course_instances')
@ -129,13 +129,6 @@ module.exports = class CourseDetailsView extends RootView
@supermodel.loadCollection @levelSessions, 'level_sessions', cache: false
@members = new CocoCollection([], { url: "/db/course_instance/#{@courseInstance.id}/members", model: User, comparator: 'nameLower' })
@listenToOnce @members, 'sync', @onMembersSync
me.set({
currentCourse: {
courseInstanceID: @courseInstance.id,
courseID: @course.id
}
})
me.patch()
@supermodel.loadCollection @members, 'members', cache: false
@owner = new User({_id: @courseInstance.get('ownerID')})
@supermodel.loadModel @owner, 'user'
@ -227,11 +220,14 @@ module.exports = class CourseDetailsView extends RootView
onClickPlayLevel: (e) ->
levelSlug = $(e.target).data('level-slug')
Backbone.Mediator.publish 'router:navigate', {
route: "/play/level/#{levelSlug}"
route: @getLevelURL levelSlug
viewClass: 'views/play/level/PlayLevelView'
viewArgs: [{courseID: @courseID, courseInstanceID: @courseInstanceID}, levelSlug]
}
getLevelURL: (levelSlug) ->
"/play/level/#{levelSlug}?course=#{@courseID}&course-instance=#{@courseInstanceID}"
onClickSaveSettings: (e) ->
return unless @courseInstance
if name = $('.settings-name-input').val()
@ -256,9 +252,9 @@ module.exports = class CourseDetailsView extends RootView
levelSlug = $(e.currentTarget).data('level-slug')
userID = $(e.currentTarget).data('user-id')
return unless levelID and levelSlug and userID
route = "/play/level/#{levelSlug}"
route = @getLevelURL levelSlug
if @userLevelSessionMap[userID]?[levelID]
route += "?session=#{@userLevelSessionMap[userID][levelID].id}&observing=true"
route += "&session=#{@userLevelSessionMap[userID][levelID].id}&observing=true"
Backbone.Mediator.publish 'router:navigate', {
route: route
viewClass: 'views/play/level/PlayLevelView'

View file

@ -127,7 +127,8 @@ module.exports = class CampaignEditorView extends RootView
rewards.push rewardObject
campaignLevel.rewards = rewards
delete campaignLevel.unlocks
campaignLevel.campaign = @campaign.get 'slug'
# Save campaign to level, unless it's a course campaign, since we reuse hero levels for course levels.
campaignLevel.campaign = @campaign.get 'slug' if @campaign.get('type', true) isnt 'course'
campaignLevels[levelOriginal] = campaignLevel
@campaign.set('levels', campaignLevels)

View file

@ -13,11 +13,6 @@ module.exports = class SaveCampaignModal extends ModalView
constructor: (options, @modelsToSave) ->
super(options)
getRenderData: ->
c = super()
c.modelsToSave = @modelsToSave
c
afterRender: ->
@$el.find('.delta-view').each((i, el) =>
$el = $(el)

View file

@ -28,9 +28,8 @@ module.exports = class ControlBarView extends CocoView
'click #control-bar-sign-up-button': 'onClickSignupButton'
constructor: (options) ->
currentCourse = me.get('currentCourse') or {}
@courseID = options.courseID or currentCourse.courseID
@courseInstanceID = options.courseInstanceID or currentCourse.courseInstanceID
@courseID = options.courseID
@courseInstanceID = options.courseInstanceID
@worldName = options.worldName
@session = options.session

View file

@ -94,8 +94,8 @@ module.exports = class PlayLevelView extends RootView
console.profile?() if PROFILE_ME
super options
@courseID = options.courseID
@courseInstanceID = options.courseInstanceID
@courseID = options.courseID or @getQueryVariable 'course'
@courseInstanceID = options.courseInstanceID or @getQueryVariable 'course-instance'
@isEditorPreview = @getQueryVariable 'dev'
@sessionID = @getQueryVariable 'session'
@ -133,7 +133,7 @@ module.exports = class PlayLevelView extends RootView
load: ->
@loadStartTime = new Date()
@god = new God debugWorker: true
@levelLoader = new LevelLoader supermodel: @supermodel, levelID: @levelID, sessionID: @sessionID, opponentSessionID: @opponentSessionID, team: @getQueryVariable('team'), observing: @observing
@levelLoader = new LevelLoader supermodel: @supermodel, levelID: @levelID, sessionID: @sessionID, opponentSessionID: @opponentSessionID, team: @getQueryVariable('team'), observing: @observing, courseID: @courseID
@listenToOnce @levelLoader, 'world-necessities-loaded', @onWorldNecessitiesLoaded
trackLevelLoadEnd: ->

View file

@ -406,12 +406,14 @@ module.exports = class HeroVictoryModal extends ModalView
# need to do something more complicated to load its slug
console.log 'have @nextLevel', @nextLevel, 'from nextLevel', nextLevel
link = "/play/level/#{@nextLevel.get('slug')}"
if @courseID
link += "?course=#{@courseID}"
link += "&course-instance=#{@courseInstanceID}" if @courseInstanceID
else if @level.get('type', true) is 'course'
link = "/courses"
if @courseID
link += "/#{@courseID}"
if @courseInstanceID
link += "/#{@courseInstanceID}"
link += "/#{@courseInstanceID}" if @courseInstanceID
else
link = '/play'
nextCampaign = @getNextLevelCampaign()
@ -428,10 +430,8 @@ module.exports = class HeroVictoryModal extends ModalView
_.merge options, extraOptions if extraOptions
if @level.get('type', true) is 'course' and @nextLevel and not options.returnToCourse
viewClass = require 'views/play/level/PlayLevelView'
if @courseID
options.courseID = @courseID
if @courseInstanceID
options.courseInstanceID = @courseInstanceID
options.courseID = @courseID
options.courseInstanceID = @courseInstanceID
viewArgs = [options, @nextLevel.get('slug')]
else if @level.get('type', true) is 'course'
# TODO: shouldn't set viewClass and route in different places
@ -440,8 +440,7 @@ module.exports = class HeroVictoryModal extends ModalView
if @courseID
viewClass = require 'views/courses/CourseDetailsView'
viewArgs.push @courseID
if @courseInstanceID
viewArgs.push @courseInstanceID
viewArgs.push @courseInstanceID if @courseInstanceID
else
viewClass = require 'views/play/CampaignView'
viewArgs = [options, @getNextLevelCampaign()]

View file

@ -9,8 +9,3 @@ module.exports = class InfiniteLoopModal extends ModalView
'click #restart-level-infinite-loop-retry-button': -> Backbone.Mediator.publish 'tome:cast-spell', {}
'click #restart-level-infinite-loop-confirm-button': -> Backbone.Mediator.publish 'level:restart', {}
'click #restart-level-infinite-loop-comment-button': -> Backbone.Mediator.publish 'tome:comment-my-code', {}
getRenderData: ->
c = super()
c.nonUserCodeProblem = @options.nonUserCodeProblem
c

View file

@ -13,12 +13,6 @@ module.exports = class SpellListEntryThangsView extends CocoView
@spell = options.spell
@avatars = []
getRenderData: (context={}) ->
context = super context
context.thangs = @thangs
context.spell = @spell
context
afterRender: ->
super()
avatar.destroy() for avatar in @avatars if @avatars

View file

@ -23,7 +23,7 @@ module.exports = class LevelGuideView extends CocoView
# A/B Testing video tutorial styles
@helpVideosIndex = me.getVideoTutorialStylesIndex(@helpVideos.length)
@helpVideo = @helpVideos[@helpVideosIndex] if @helpVideos.length > 0
@videoLocked = not @helpVideo?.free and @requiresSubscription
@videoLocked = not (@helpVideo?.free or options.level.get('type', true) is 'course') and @requiresSubscription
@firstOnly = options.firstOnly
@docs = options?.docs ? options.level.get('documentation') ? {}

View file

@ -11,10 +11,6 @@ module.exports = class SaveLoadView extends CocoView
events:
'change #save-granularity-toggle input': 'onSaveGranularityChanged'
getRenderData: (context={}) ->
context = super(context)
context
afterRender: ->
super()

View file

@ -14,10 +14,6 @@ module.exports = class PlaySettingsModal extends ModalView
constructor: (options) ->
super options
getRenderData: (context={}) ->
context = super(context)
context
afterRender: ->
super()
return unless @supermodel.finished()

View file

@ -7,18 +7,13 @@ module.exports = class JobProfileCodeModal extends ModalView
template: template
modalWidthPercent: 90
plain: true
constructor: (options) ->
super(arguments...)
@session = options.session
getRenderData: ->
c = super()
c.session = @session
c
afterRender: ->
super()
codeView = new LevelSessionCodeView({session:@session})
@insertSubView(codeView, @$el.find('.level-session-code-view'))

View file

@ -11,6 +11,7 @@ CampaignHandler = class CampaignHandler extends Handler
'name'
'fullName'
'description'
'type'
'i18n'
'i18nCoverage'
'ambientSound'

View file

@ -23,6 +23,7 @@ createMailContext = (req, done) ->
user = req.user
recipientID = req.body.recipientID
subject = req.body.subject
country = req.body.country
level = if user?.get('points') > 0 then Math.floor(5 * Math.log((1 / 100) * (user.get('points') + 100))) + 1 else 0
premium = user?.isPremium()
@ -30,7 +31,7 @@ createMailContext = (req, done) ->
#{message}
--
<a href='http://codecombat.com/user/#{user.get('slug') or user.get('_id')}'>#{user.get('name') or 'Anonymous'}</a> - Level #{level}#{if premium then ' - Subscriber' else ''}
<a href='http://codecombat.com/user/#{user.get('slug') or user.get('_id')}'>#{user.get('name') or 'Anonymous'}</a> - Level #{level}#{if premium then ' - Subscriber' else ''}#{if country then ' - ' + country else ''}
"""
if req.body.browser
content += "\n#{req.body.browser} - #{req.body.screenSize}"
@ -46,7 +47,6 @@ createMailContext = (req, done) ->
email_data:
subject: "[CodeCombat] #{subject ? ('Feedback - ' + (sender or user.get('email')))}"
content: content
if recipientID and (user.isAdmin() or ('employer' in (user.get('permissions') ? [])))
User.findById(recipientID, 'email').exec (err, document) ->
if err

View file

@ -17,10 +17,6 @@ UserSchema = new mongoose.Schema({
dateCreated:
type: Date
'default': Date.now
currentCourse: {
courseID: mongoose.Schema.Types.ObjectId
courseInstanceID: mongoose.Schema.Types.ObjectId
}
}, {strict: false})
UserSchema.index({'dateCreated': 1})
@ -323,7 +319,7 @@ UserSchema.statics.editableProperties = [
'firstName', 'lastName', 'gender', 'ageRange', 'facebookID', 'gplusID', 'emails',
'testGroupNumber', 'music', 'hourOfCode', 'hourOfCodeComplete', 'preferredLanguage',
'wizard', 'aceConfig', 'autocastDelay', 'lastLevel', 'jobProfile', 'savedEmployerFilterAlerts',
'heroConfig', 'iosIdentifierForVendor', 'siteref', 'referrer', 'currentCourse'
'heroConfig', 'iosIdentifierForVendor', 'siteref', 'referrer'
]
UserSchema.plugin plugins.NamedPlugin