mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-27 17:45:40 -05:00
Merge branch 'master' into production
This commit is contained in:
commit
44fb30b81e
27 changed files with 63 additions and 88 deletions
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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' }
|
||||
|
|
|
@ -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({})
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(", ")})
|
||||
|
|
|
@ -7,8 +7,3 @@ module.exports = class CareersView extends RootView
|
|||
|
||||
constructor: (options, @position) ->
|
||||
super options
|
||||
|
||||
getRenderData: ->
|
||||
context = super()
|
||||
context.position = @position
|
||||
context
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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: ->
|
||||
|
|
|
@ -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()]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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') ? {}
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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'))
|
||||
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ CampaignHandler = class CampaignHandler extends Handler
|
|||
'name'
|
||||
'fullName'
|
||||
'description'
|
||||
'type'
|
||||
'i18n'
|
||||
'i18nCoverage'
|
||||
'ambientSound'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue