Merge pull request #1221 from codecombat/master

Master into production
This commit is contained in:
Michael Schmatz 2014-06-24 11:30:28 -07:00
commit 22b7e52f98
19 changed files with 88 additions and 25 deletions

View file

@ -1,8 +1,8 @@
module.exports = initializeGoogle = -> module.exports = initializeGoogle = ->
onGPlusLoaded = -> window.onGPlusLoaded = ->
Backbone.Mediator.publish "gapi-loaded" Backbone.Mediator.publish "gapi-loaded"
return return
signinCallback = (authResult) -> window.signinCallback = (authResult) ->
Backbone.Mediator.publish "gplus-logged-in", authResult if authResult["access_token"] Backbone.Mediator.publish "gplus-logged-in", authResult if authResult["access_token"]
return return
(-> (->

View file

@ -288,10 +288,13 @@ module.exports = class Camera extends CocoClass
boundTarget: (pos, zoom) -> boundTarget: (pos, zoom) ->
# Given an {x, y} in Surface coordinates, return one that will keep our viewport on the Surface. # Given an {x, y} in Surface coordinates, return one that will keep our viewport on the Surface.
return pos unless @bounds return pos unless @bounds
y = pos.y
if thang = pos.sprite?.thang
y = @worldToSurface(x: thang.pos.x, y: thang.pos.y).y # ignore z
marginX = (@canvasWidth / zoom / 2) marginX = (@canvasWidth / zoom / 2)
marginY = (@canvasHeight / zoom / 2) marginY = (@canvasHeight / zoom / 2)
x = Math.min(Math.max(marginX + @bounds.x, pos.x + @offset.x), @bounds.x + @bounds.width - marginX) x = Math.min(Math.max(marginX + @bounds.x, pos.x + @offset.x), @bounds.x + @bounds.width - marginX)
y = Math.min(Math.max(marginY + @bounds.y, pos.y + @offset.y), @bounds.y + @bounds.height - marginY) y = Math.min(Math.max(marginY + @bounds.y, y + @offset.y), @bounds.y + @bounds.height - marginY)
{x: x, y: y} {x: x, y: y}
updateViewports: (target) -> updateViewports: (target) ->

View file

@ -64,7 +64,7 @@ class CocoModel extends Backbone.Model
@backedUp = {} @backedUp = {}
schema: -> return @constructor.schema schema: -> return @constructor.schema
getValidationErrors: -> getValidationErrors: ->
errors = tv4.validateMultiple(@attributes, @constructor.schema or {}).errors errors = tv4.validateMultiple(@attributes, @constructor.schema or {}).errors
return errors if errors?.length return errors if errors?.length
@ -76,7 +76,7 @@ class CocoModel extends Backbone.Model
for error in errors for error in errors
console.debug "\t", error.dataPath, ":", error.message console.debug "\t", error.dataPath, ":", error.message
return errors return errors
save: (attrs, options) -> save: (attrs, options) ->
options ?= {} options ?= {}
options.headers ?= {} options.headers ?= {}
@ -97,19 +97,19 @@ class CocoModel extends Backbone.Model
noty text: "#{errorMessage}: #{res.status} #{res.statusText}", layout: 'topCenter', type: 'error', killer: false, timeout: 10000 noty text: "#{errorMessage}: #{res.status} #{res.statusText}", layout: 'topCenter', type: 'error', killer: false, timeout: 10000
@trigger "save", @ @trigger "save", @
return super attrs, options return super attrs, options
patch: (options) -> patch: (options) ->
return false unless @_revertAttributes return false unless @_revertAttributes
options ?= {} options ?= {}
options.patch = true options.patch = true
attrs = {_id: @id} attrs = {_id: @id}
keys = [] keys = []
for key in _.keys @attributes for key in _.keys @attributes
unless _.isEqual @attributes[key], @_revertAttributes[key] unless _.isEqual @attributes[key], @_revertAttributes[key]
attrs[key] = @attributes[key] attrs[key] = @attributes[key]
keys.push key keys.push key
return unless keys.length return unless keys.length
console.debug 'Patching', @get('name') or @, keys console.debug 'Patching', @get('name') or @, keys
@save(attrs, options) @save(attrs, options)
@ -207,7 +207,10 @@ class CocoModel extends Backbone.Model
applyDelta: (delta) -> applyDelta: (delta) ->
newAttributes = $.extend(true, {}, @attributes) newAttributes = $.extend(true, {}, @attributes)
jsondiffpatch.patch newAttributes, delta try
jsondiffpatch.patch newAttributes, delta
catch error
console.error "Error applying delta", delta, "to attributes", newAttributes, error
@set newAttributes @set newAttributes
getExpandedDelta: -> getExpandedDelta: ->

View file

@ -122,7 +122,7 @@ module.exports = class SuperModel extends Backbone.Model
return @progress is 1.0 or not @denom return @progress is 1.0 or not @denom
addModelResource: (modelOrCollection, name, fetchOptions, value=1) -> addModelResource: (modelOrCollection, name, fetchOptions, value=1) ->
modelOrCollection.saveBackups = @shouldSaveBackups(modelOrCollection) modelOrCollection.saveBackups = modelOrCollection.saveBackups or @shouldSaveBackups(modelOrCollection)
@checkName(name) @checkName(name)
res = new ModelResource(modelOrCollection, name, fetchOptions, value) res = new ModelResource(modelOrCollection, name, fetchOptions, value)
@storeResource(res, value) @storeResource(res, value)

View file

@ -49,6 +49,9 @@
tr tr
cursor: pointer cursor: pointer
tr.expired
opacity: 0.5
code code
background-color: rgb(220, 220, 220) background-color: rgb(220, 220, 220)
color: #555 color: #555

View file

@ -1,5 +1,21 @@
#docs-modal .modal-dialog #docs-modal .modal-dialog
width: 800px width: 800px
.tab-content
padding-top: 20px
li:not(.active) a[data-toggle="tab"] li:not(.active) a[data-toggle="tab"]
cursor: pointer cursor: pointer
img
display: block
margin: 0 auto
em
display: block
margin: 0 auto
text-align: center
hr
border-color: #5c5c5c
width: 80%

View file

@ -93,7 +93,9 @@ block content
for candidate, index in area.candidates for candidate, index in area.candidates
- var profile = candidate.get('jobProfile'); - var profile = candidate.get('jobProfile');
- var authorized = candidate.id; // If we have the id, then we are authorized. - var authorized = candidate.id; // If we have the id, then we are authorized.
tr(data-candidate-id=candidate.id, id=candidate.id) - 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 td
if authorized if authorized
img(src=candidate.getPhotoURL(50), alt=profile.name, title=profile.name, height=50) img(src=candidate.getPhotoURL(50), alt=profile.name, title=profile.name, height=50)
@ -115,7 +117,7 @@ block content
code= skill code= skill
span span
td= profile.experience td= profile.experience
td(data-profile-age=(new Date() - new Date(profile.updated)) / 86400 / 1000)= moment(profile.updated).fromNow() td(data-profile-age=profileAge)= moment(profile.updated).fromNow()
if me.isAdmin() if me.isAdmin()
td= remarks[candidate.id] ? remarks[candidate.id].get('contactName') : '' td= remarks[candidate.id] ? remarks[candidate.id].get('contactName') : ''
if me.isAdmin() && area.id == 'inactive-candidates' if me.isAdmin() && area.id == 'inactive-candidates'

View file

@ -22,7 +22,11 @@ block content
p p
strong Tournament ended! strong Tournament ended!
a(href="#winners") Behold the winners a(href="#winners") Behold the winners
| . Thanks for playing! | . Thanks for playing! You can
strong still play
| Greed and all of our other
a(href="/play/ladder") multiplayer arenas
| .
p p
| Want to commiserate? Head over to | Want to commiserate? Head over to
a(href="http://discourse.codecombat.com/") the forum a(href="http://discourse.codecombat.com/") the forum

View file

@ -1,7 +1,7 @@
ul#primary-goals-list ul#primary-goals-list
div.goals-status div.goals-status
strong(data-i18n="play_level.goals") Goals strong(data-i18n="play_level.goals") Goals
span.spl.spr : span.spr :
span(data-i18n="play_level.success").secret.goal-status.success Success! span(data-i18n="play_level.success").secret.goal-status.success Success!
span(data-i18n="play_level.incomplete").secret.goal-status.incomplete Incomplete span(data-i18n="play_level.incomplete").secret.goal-status.incomplete Incomplete
span(data-i18n="play_level.timed_out").secret.goal-status.timed-out Ran out of time span(data-i18n="play_level.timed_out").secret.goal-status.timed-out Ran out of time

View file

@ -22,6 +22,7 @@ adminContacts = [
{id: "5162fab9c92b4c751e000274", name: "Scott"} {id: "5162fab9c92b4c751e000274", name: "Scott"}
{id: "51eb2714fa058cb20d0006ef", name: "Michael"} {id: "51eb2714fa058cb20d0006ef", name: "Michael"}
{id: "51538fdb812dd9af02000001", name: "George"} {id: "51538fdb812dd9af02000001", name: "George"}
{id: "52a57252a89409700d0000d9", name: "Ignore"}
] ]
module.exports = class ProfileView extends View module.exports = class ProfileView extends View
@ -104,6 +105,7 @@ module.exports = class ProfileView extends View
@linkedinLoaded = true @linkedinLoaded = true
if @waitingForLinkedIn if @waitingForLinkedIn
@renderLinkedInButton() @renderLinkedInButton()
@authorizedWithLinkedIn = IN?.User?.isAuthorized()
renderLinkedInButton: => renderLinkedInButton: =>
IN?.parse() IN?.parse()

View file

@ -81,7 +81,7 @@ module.exports = class ArticleEditView extends View
@preview.focus() if window.focus @preview.focus() if window.focus
@preview.onload = => @pushChangesToPreview() @preview.onload = => @pushChangesToPreview()
return false return false
openSaveModal: -> openSaveModal: ->
@openModalView(new SaveVersionModal({model: @article})) @openModalView(new SaveVersionModal({model: @article}))
@ -101,6 +101,7 @@ module.exports = class ArticleEditView extends View
@disableModalInProgress(modal) @disableModalInProgress(modal)
res.success => res.success =>
@article.clearBackup()
modal.modal('hide') modal.modal('hide')
url = "/editor/article/#{newArticle.get('slug') or newArticle.id}" url = "/editor/article/#{newArticle.get('slug') or newArticle.id}"
document.location.href = url document.location.href = url

View file

@ -76,7 +76,7 @@ module.exports = class LevelSaveView extends SaveVersionModal
else if @level.isPublished() and not newModel.isPublished() else if @level.isPublished() and not newModel.isPublished()
newModel.publish() # Publish any LevelComponents that weren't published yet newModel.publish() # Publish any LevelComponents that weren't published yet
formsToSave.push form formsToSave.push form
for model in modelsToSave for model in modelsToSave
if errors = model.getValidationErrors() if errors = model.getValidationErrors()
messages = ("\t #{error.dataPath}: #{error.message}" for error in errors) messages = ("\t #{error.dataPath}: #{error.message}" for error in errors)
@ -96,6 +96,8 @@ module.exports = class LevelSaveView extends SaveVersionModal
forms.applyErrorsToForm($(form), JSON.parse(res.responseText)) forms.applyErrorsToForm($(form), JSON.parse(res.responseText))
res.success => res.success =>
modelsToSave = _.without modelsToSave, newModel modelsToSave = _.without modelsToSave, newModel
oldModel = _.find @supermodel.models, (m) -> m.get('original') is newModel.get('original')
oldModel.clearBackup() # Otherwise looking at old versions is confusing.
unless modelsToSave.length unless modelsToSave.length
url = "/editor/level/#{@level.get('slug') or @level.id}" url = "/editor/level/#{@level.get('slug') or @level.id}"
document.location.href = url document.location.href = url

View file

@ -66,7 +66,7 @@ module.exports = class ThangTypeEditView extends View
raw = ("raw:#{name}" for name in raw) raw = ("raw:#{name}" for name in raw)
main = _.keys(@thangType.get('actions') or {}) main = _.keys(@thangType.get('actions') or {})
main.concat(raw) main.concat(raw)
afterRender: -> afterRender: ->
super() super()
return unless @supermodel.finished() return unless @supermodel.finished()
@ -326,7 +326,9 @@ module.exports = class ThangTypeEditView extends View
image = @currentSprite.imageObject.image image = @currentSprite.imageObject.image
portraitSource = imageToPortrait image portraitSource = imageToPortrait image
# bit of a hacky way to get that portrait # bit of a hacky way to get that portrait
success = -> document.location.href = url success = =>
@thangType.clearBackup()
document.location.href = url
newThangType.uploadGenericPortrait success, portraitSource newThangType.uploadGenericPortrait success, portraitSource
clearRawData: -> clearRawData: ->
@ -419,4 +421,4 @@ imageToPortrait = (img) ->
scaleY = 100 / img.height scaleY = 100 / img.height
ctx.scale scaleX, scaleY ctx.scale scaleX, scaleY
ctx.drawImage img, 0, 0 ctx.drawImage img, 0, 0
canvas.toDataURL("image/png") canvas.toDataURL("image/png")

View file

@ -14,9 +14,9 @@ module.exports = class GoldView extends View
super options super options
@teamGold = {} @teamGold = {}
@teamGoldEarned = {} @teamGoldEarned = {}
@shownOnce = false
onGoldChanged: (e) -> onGoldChanged: (e) ->
@$el.show()
return if @teamGold[e.team] is e.gold and @teamGoldEarned[e.team] is e.goldEarned return if @teamGold[e.team] is e.gold and @teamGoldEarned[e.team] is e.goldEarned
@teamGold[e.team] = e.gold @teamGold[e.team] = e.gold
@teamGoldEarned[e.team] = e.goldEarned @teamGoldEarned[e.team] = e.goldEarned
@ -30,9 +30,11 @@ module.exports = class GoldView extends View
text += " (#{e.goldEarned})" text += " (#{e.goldEarned})"
goldEl.text text goldEl.text text
@updateTitle() @updateTitle()
@$el.show()
@shownOnce = true
updateTitle: -> updateTitle: ->
@$el.attr 'title', ("Team '#{team}' has #{gold} now of #{@teamGoldEarned[team]} gold earned." for team, gold of @teamGold).join ' ' @$el.attr 'title', ("Team '#{team}' has #{gold} now of #{@teamGoldEarned[team]} gold earned." for team, gold of @teamGold).join ' '
onSetLetterbox: (e) -> onSetLetterbox: (e) ->
@$el.toggle not e.on @$el.toggle not e.on if @shownOnce

View file

@ -8,6 +8,7 @@ utils = require 'lib/utils'
module.exports = class DocsModal extends View module.exports = class DocsModal extends View
template: template template: template
id: 'docs-modal' id: 'docs-modal'
plain: true
shortcuts: shortcuts:
'enter': 'hide' 'enter': 'hide'

View file

@ -180,8 +180,8 @@ module.exports = class SpellView extends View
return (owner is 'this' or owner is 'more') and (not doc.owner? or doc.owner is 'this') 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 console.log 'could not find doc for', prop, 'from', e.allDocs['__' + prop], 'for', owner, 'of', e.propGroups unless doc
doc ?= prop doc ?= prop
if doc.snippets? if doc.snippets?[@spell.language]
entry = entry =
content: doc.snippets[@spell.language].code content: doc.snippets[@spell.language].code
name: doc.name name: doc.name
tabTrigger: doc.snippets[@spell.language].tab tabTrigger: doc.snippets[@spell.language].tab

View file

@ -3,6 +3,7 @@ fs = require 'fs'
request = require 'request' request = require 'request'
mongoose = require('mongoose') mongoose = require('mongoose')
errors = require '../commons/errors' errors = require '../commons/errors'
config = require '../../server_config'
module.exports.setup = (app) -> module.exports.setup = (app) ->
app.all '/file*', (req, res) -> app.all '/file*', (req, res) ->
@ -130,6 +131,8 @@ checkExistence = (options, req, res, force, done) ->
errors.conflict(res, {canForce:userCanEditFile(req.user, file)}) errors.conflict(res, {canForce:userCanEditFile(req.user, file)})
done(true) done(true)
else if file else if file
fullPath = "/file/#{options.metadata.path}/#{options.filename}"
clearCloudFlareCacheForFile(fullPath)
q = { _id: file._id } q = { _id: file._id }
q.root = 'media' q.root = 'media'
Grid.gfs.remove q, (err) -> Grid.gfs.remove q, (err) ->
@ -172,3 +175,20 @@ createPostOptions = (req) ->
options.metadata.description = req.body.description if req.body.description? options.metadata.description = req.body.description if req.body.description?
options options
clearCloudFlareCacheForFile = (path='/file') ->
unless config.cloudflare.token
console.log 'skipping clearing cloud cache, not configured'
return
request = require 'request'
r = request.post 'https://www.cloudflare.com/api_json.html', (err, httpResponse, body) ->
if (err)
console.error('CloudFlare file cache clear failed:', body)
form = r.form()
form.append 'tkn', config.cloudflare.token
form.append 'email', 'scott@codecombat.com'
form.append 'z', 'codecombat.com'
form.append 'a', 'zone_file_purge'
form.append 'url', "http://codecombat.com#{path}"

View file

@ -308,9 +308,9 @@ UserHandler = class UserHandler extends Handler
getCandidates: (req, res) -> getCandidates: (req, res) ->
authorized = req.user.isAdmin() or ('employer' in req.user.get('permissions')) authorized = req.user.isAdmin() or ('employer' in req.user.get('permissions'))
since = (new Date((new Date()) - 2 * 30.4 * 86400 * 1000)).toISOString() months = if req.user.isAdmin() then 12 else 2
since = (new Date((new Date()) - months * 30.4 * 86400 * 1000)).toISOString()
query = {'jobProfile.updated': {$gt: since}} query = {'jobProfile.updated': {$gt: since}}
#query.jobProfileApproved = true unless req.user.isAdmin() # We split into featured and other now.
query['jobProfile.active'] = true unless req.user.isAdmin() query['jobProfile.active'] = true unless req.user.isAdmin()
selection = 'jobProfile jobProfileApproved photoURL' selection = 'jobProfile jobProfileApproved photoURL'
selection += ' email name' if authorized selection += ' email name' if authorized

View file

@ -4,6 +4,8 @@ config.unittest = process.argv.indexOf("--unittest") > -1
config.port = process.env.COCO_PORT or process.env.COCO_NODE_PORT or 3000 config.port = process.env.COCO_PORT or process.env.COCO_NODE_PORT or 3000
config.ssl_port = process.env.COCO_SSL_PORT or process.env.COCO_SSL_NODE_PORT or 3443 config.ssl_port = process.env.COCO_SSL_PORT or process.env.COCO_SSL_NODE_PORT or 3443
config.cloudflare =
token: process.env.COCO_CLOUDFLARE_API_KEY or ''
config.mongo = config.mongo =
port: process.env.COCO_MONGO_PORT or 27017 port: process.env.COCO_MONGO_PORT or 27017