mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-29 18:45:48 -05:00
commit
22b7e52f98
19 changed files with 88 additions and 25 deletions
|
@ -1,8 +1,8 @@
|
|||
module.exports = initializeGoogle = ->
|
||||
onGPlusLoaded = ->
|
||||
window.onGPlusLoaded = ->
|
||||
Backbone.Mediator.publish "gapi-loaded"
|
||||
return
|
||||
signinCallback = (authResult) ->
|
||||
window.signinCallback = (authResult) ->
|
||||
Backbone.Mediator.publish "gplus-logged-in", authResult if authResult["access_token"]
|
||||
return
|
||||
(->
|
||||
|
|
|
@ -288,10 +288,13 @@ module.exports = class Camera extends CocoClass
|
|||
boundTarget: (pos, zoom) ->
|
||||
# Given an {x, y} in Surface coordinates, return one that will keep our viewport on the Surface.
|
||||
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)
|
||||
marginY = (@canvasHeight / zoom / 2)
|
||||
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}
|
||||
|
||||
updateViewports: (target) ->
|
||||
|
|
|
@ -64,7 +64,7 @@ class CocoModel extends Backbone.Model
|
|||
|
||||
@backedUp = {}
|
||||
schema: -> return @constructor.schema
|
||||
|
||||
|
||||
getValidationErrors: ->
|
||||
errors = tv4.validateMultiple(@attributes, @constructor.schema or {}).errors
|
||||
return errors if errors?.length
|
||||
|
@ -76,7 +76,7 @@ class CocoModel extends Backbone.Model
|
|||
for error in errors
|
||||
console.debug "\t", error.dataPath, ":", error.message
|
||||
return errors
|
||||
|
||||
|
||||
save: (attrs, options) ->
|
||||
options ?= {}
|
||||
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
|
||||
@trigger "save", @
|
||||
return super attrs, options
|
||||
|
||||
|
||||
patch: (options) ->
|
||||
return false unless @_revertAttributes
|
||||
options ?= {}
|
||||
options.patch = true
|
||||
|
||||
|
||||
attrs = {_id: @id}
|
||||
keys = []
|
||||
for key in _.keys @attributes
|
||||
unless _.isEqual @attributes[key], @_revertAttributes[key]
|
||||
attrs[key] = @attributes[key]
|
||||
keys.push key
|
||||
|
||||
|
||||
return unless keys.length
|
||||
console.debug 'Patching', @get('name') or @, keys
|
||||
@save(attrs, options)
|
||||
|
@ -207,7 +207,10 @@ class CocoModel extends Backbone.Model
|
|||
|
||||
applyDelta: (delta) ->
|
||||
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
|
||||
|
||||
getExpandedDelta: ->
|
||||
|
|
|
@ -122,7 +122,7 @@ module.exports = class SuperModel extends Backbone.Model
|
|||
return @progress is 1.0 or not @denom
|
||||
|
||||
addModelResource: (modelOrCollection, name, fetchOptions, value=1) ->
|
||||
modelOrCollection.saveBackups = @shouldSaveBackups(modelOrCollection)
|
||||
modelOrCollection.saveBackups = modelOrCollection.saveBackups or @shouldSaveBackups(modelOrCollection)
|
||||
@checkName(name)
|
||||
res = new ModelResource(modelOrCollection, name, fetchOptions, value)
|
||||
@storeResource(res, value)
|
||||
|
|
|
@ -49,6 +49,9 @@
|
|||
tr
|
||||
cursor: pointer
|
||||
|
||||
tr.expired
|
||||
opacity: 0.5
|
||||
|
||||
code
|
||||
background-color: rgb(220, 220, 220)
|
||||
color: #555
|
||||
|
|
|
@ -1,5 +1,21 @@
|
|||
#docs-modal .modal-dialog
|
||||
width: 800px
|
||||
|
||||
.tab-content
|
||||
padding-top: 20px
|
||||
|
||||
li:not(.active) a[data-toggle="tab"]
|
||||
cursor: pointer
|
||||
|
||||
img
|
||||
display: block
|
||||
margin: 0 auto
|
||||
|
||||
em
|
||||
display: block
|
||||
margin: 0 auto
|
||||
text-align: center
|
||||
|
||||
hr
|
||||
border-color: #5c5c5c
|
||||
width: 80%
|
|
@ -93,7 +93,9 @@ block content
|
|||
for candidate, index in area.candidates
|
||||
- var profile = candidate.get('jobProfile');
|
||||
- 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
|
||||
if authorized
|
||||
img(src=candidate.getPhotoURL(50), alt=profile.name, title=profile.name, height=50)
|
||||
|
@ -115,7 +117,7 @@ block content
|
|||
code= skill
|
||||
span
|
||||
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()
|
||||
td= remarks[candidate.id] ? remarks[candidate.id].get('contactName') : ''
|
||||
if me.isAdmin() && area.id == 'inactive-candidates'
|
||||
|
|
|
@ -22,7 +22,11 @@ block content
|
|||
p
|
||||
strong Tournament ended!
|
||||
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
|
||||
| Want to commiserate? Head over to
|
||||
a(href="http://discourse.codecombat.com/") the forum
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
ul#primary-goals-list
|
||||
div.goals-status
|
||||
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.incomplete").secret.goal-status.incomplete Incomplete
|
||||
span(data-i18n="play_level.timed_out").secret.goal-status.timed-out Ran out of time
|
||||
|
|
|
@ -22,6 +22,7 @@ adminContacts = [
|
|||
{id: "5162fab9c92b4c751e000274", name: "Scott"}
|
||||
{id: "51eb2714fa058cb20d0006ef", name: "Michael"}
|
||||
{id: "51538fdb812dd9af02000001", name: "George"}
|
||||
{id: "52a57252a89409700d0000d9", name: "Ignore"}
|
||||
]
|
||||
|
||||
module.exports = class ProfileView extends View
|
||||
|
@ -104,6 +105,7 @@ module.exports = class ProfileView extends View
|
|||
@linkedinLoaded = true
|
||||
if @waitingForLinkedIn
|
||||
@renderLinkedInButton()
|
||||
@authorizedWithLinkedIn = IN?.User?.isAuthorized()
|
||||
|
||||
renderLinkedInButton: =>
|
||||
IN?.parse()
|
||||
|
|
|
@ -81,7 +81,7 @@ module.exports = class ArticleEditView extends View
|
|||
@preview.focus() if window.focus
|
||||
@preview.onload = => @pushChangesToPreview()
|
||||
return false
|
||||
|
||||
|
||||
openSaveModal: ->
|
||||
@openModalView(new SaveVersionModal({model: @article}))
|
||||
|
||||
|
@ -101,6 +101,7 @@ module.exports = class ArticleEditView extends View
|
|||
@disableModalInProgress(modal)
|
||||
|
||||
res.success =>
|
||||
@article.clearBackup()
|
||||
modal.modal('hide')
|
||||
url = "/editor/article/#{newArticle.get('slug') or newArticle.id}"
|
||||
document.location.href = url
|
||||
|
|
|
@ -76,7 +76,7 @@ module.exports = class LevelSaveView extends SaveVersionModal
|
|||
else if @level.isPublished() and not newModel.isPublished()
|
||||
newModel.publish() # Publish any LevelComponents that weren't published yet
|
||||
formsToSave.push form
|
||||
|
||||
|
||||
for model in modelsToSave
|
||||
if errors = model.getValidationErrors()
|
||||
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))
|
||||
res.success =>
|
||||
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
|
||||
url = "/editor/level/#{@level.get('slug') or @level.id}"
|
||||
document.location.href = url
|
||||
|
|
|
@ -66,7 +66,7 @@ module.exports = class ThangTypeEditView extends View
|
|||
raw = ("raw:#{name}" for name in raw)
|
||||
main = _.keys(@thangType.get('actions') or {})
|
||||
main.concat(raw)
|
||||
|
||||
|
||||
afterRender: ->
|
||||
super()
|
||||
return unless @supermodel.finished()
|
||||
|
@ -326,7 +326,9 @@ module.exports = class ThangTypeEditView extends View
|
|||
image = @currentSprite.imageObject.image
|
||||
portraitSource = imageToPortrait image
|
||||
# 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
|
||||
|
||||
clearRawData: ->
|
||||
|
@ -419,4 +421,4 @@ imageToPortrait = (img) ->
|
|||
scaleY = 100 / img.height
|
||||
ctx.scale scaleX, scaleY
|
||||
ctx.drawImage img, 0, 0
|
||||
canvas.toDataURL("image/png")
|
||||
canvas.toDataURL("image/png")
|
||||
|
|
|
@ -14,9 +14,9 @@ module.exports = class GoldView extends View
|
|||
super options
|
||||
@teamGold = {}
|
||||
@teamGoldEarned = {}
|
||||
@shownOnce = false
|
||||
|
||||
onGoldChanged: (e) ->
|
||||
@$el.show()
|
||||
return if @teamGold[e.team] is e.gold and @teamGoldEarned[e.team] is e.goldEarned
|
||||
@teamGold[e.team] = e.gold
|
||||
@teamGoldEarned[e.team] = e.goldEarned
|
||||
|
@ -30,9 +30,11 @@ module.exports = class GoldView extends View
|
|||
text += " (#{e.goldEarned})"
|
||||
goldEl.text text
|
||||
@updateTitle()
|
||||
@$el.show()
|
||||
@shownOnce = true
|
||||
|
||||
updateTitle: ->
|
||||
@$el.attr 'title', ("Team '#{team}' has #{gold} now of #{@teamGoldEarned[team]} gold earned." for team, gold of @teamGold).join ' '
|
||||
|
||||
onSetLetterbox: (e) ->
|
||||
@$el.toggle not e.on
|
||||
@$el.toggle not e.on if @shownOnce
|
||||
|
|
|
@ -8,6 +8,7 @@ utils = require 'lib/utils'
|
|||
module.exports = class DocsModal extends View
|
||||
template: template
|
||||
id: 'docs-modal'
|
||||
plain: true
|
||||
|
||||
shortcuts:
|
||||
'enter': 'hide'
|
||||
|
|
|
@ -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')
|
||||
console.log 'could not find doc for', prop, 'from', e.allDocs['__' + prop], 'for', owner, 'of', e.propGroups unless doc
|
||||
doc ?= prop
|
||||
if doc.snippets?
|
||||
entry =
|
||||
if doc.snippets?[@spell.language]
|
||||
entry =
|
||||
content: doc.snippets[@spell.language].code
|
||||
name: doc.name
|
||||
tabTrigger: doc.snippets[@spell.language].tab
|
||||
|
|
|
@ -3,6 +3,7 @@ fs = require 'fs'
|
|||
request = require 'request'
|
||||
mongoose = require('mongoose')
|
||||
errors = require '../commons/errors'
|
||||
config = require '../../server_config'
|
||||
|
||||
module.exports.setup = (app) ->
|
||||
app.all '/file*', (req, res) ->
|
||||
|
@ -130,6 +131,8 @@ checkExistence = (options, req, res, force, done) ->
|
|||
errors.conflict(res, {canForce:userCanEditFile(req.user, file)})
|
||||
done(true)
|
||||
else if file
|
||||
fullPath = "/file/#{options.metadata.path}/#{options.filename}"
|
||||
clearCloudFlareCacheForFile(fullPath)
|
||||
q = { _id: file._id }
|
||||
q.root = 'media'
|
||||
Grid.gfs.remove q, (err) ->
|
||||
|
@ -172,3 +175,20 @@ createPostOptions = (req) ->
|
|||
options.metadata.description = req.body.description if req.body.description?
|
||||
|
||||
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}"
|
||||
|
|
|
@ -308,9 +308,9 @@ UserHandler = class UserHandler extends Handler
|
|||
|
||||
getCandidates: (req, res) ->
|
||||
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.jobProfileApproved = true unless req.user.isAdmin() # We split into featured and other now.
|
||||
query['jobProfile.active'] = true unless req.user.isAdmin()
|
||||
selection = 'jobProfile jobProfileApproved photoURL'
|
||||
selection += ' email name' if authorized
|
||||
|
|
|
@ -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.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 =
|
||||
port: process.env.COCO_MONGO_PORT or 27017
|
||||
|
|
Loading…
Reference in a new issue