Merge branch 'master' into production

This commit is contained in:
Nick Winter 2014-06-01 11:45:27 -07:00
commit 9cbc6028b2
15 changed files with 82 additions and 67 deletions

View file

@ -5,6 +5,7 @@ BEEN_HERE_BEFORE_KEY = 'beenHereBefore'
init = -> init = ->
module.exports.me = window.me = new User(window.userObject) # inserted into main.html module.exports.me = window.me = new User(window.userObject) # inserted into main.html
module.exports.me.onLoaded()
trackFirstArrival() trackFirstArrival()
if me and not me.get('testGroupNumber')? if me and not me.get('testGroupNumber')?
# Assign testGroupNumber to returning visitors; new ones in server/routes/auth # Assign testGroupNumber to returning visitors; new ones in server/routes/auth

View file

@ -162,10 +162,14 @@ groupDeltasByAffectingPaths = (deltas) ->
prunedMap prunedMap
module.exports.pruneConflictsFromDelta = (delta, conflicts) -> module.exports.pruneConflictsFromDelta = (delta, conflicts) ->
expandedDeltas = (conflict.pendingDelta for conflict in conflicts)
module.exports.pruneExpandedDeltasFromDelta delta, expandedDeltas
module.exports.pruneExpandedDeltasFromDelta = (delta, expandedDeltas) ->
# the jsondiffpatch delta mustn't include any dangling nodes, # the jsondiffpatch delta mustn't include any dangling nodes,
# or else things will get removed which shouldn't be, or errors will occur # or else things will get removed which shouldn't be, or errors will occur
for conflict in conflicts for expandedDelta in expandedDeltas
prunePath delta, conflict.pendingDelta.deltaPath prunePath delta, expandedDelta.deltaPath
if _.isEmpty delta then undefined else delta if _.isEmpty delta then undefined else delta
prunePath = (delta, path) -> prunePath = (delta, path) ->

View file

@ -34,5 +34,5 @@ module.exports.applyErrorsToForm = (el, errors) ->
module.exports.clearFormAlerts = (el) -> module.exports.clearFormAlerts = (el) ->
$('.has-error', el).removeClass('has-error') $('.has-error', el).removeClass('has-error')
$('.alert', el).remove() $('.alert.alert-danger', el).remove()
el.find('.help-block.error-help-block').remove() el.find('.help-block.error-help-block').remove()

View file

@ -78,7 +78,7 @@ module.exports = nativeDescription: "magyar", englishDescription: "Hungarian", t
creating: "Fiók létrehozása" creating: "Fiók létrehozása"
sign_up: "Regisztráció" sign_up: "Regisztráció"
log_in: "Belépés meglévő fiókkal" log_in: "Belépés meglévő fiókkal"
# social_signup: "Or, you can sign up through Facebook or G+:" social_signup: "De regisztrálhatsz a Facebook-on vagy a G+:-on keresztül is."
# required: "You need to log in before you can go that way." # required: "You need to log in before you can go that way."
home: home:
@ -166,10 +166,10 @@ module.exports = nativeDescription: "magyar", englishDescription: "Hungarian", t
email_announcements_description: "Szeretnél levelet kapni a legújabb fejlesztéseinkről?" email_announcements_description: "Szeretnél levelet kapni a legújabb fejlesztéseinkről?"
email_notifications: "Értesítések" email_notifications: "Értesítések"
# email_notifications_summary: "Controls for personalized, automatic email notifications related to your CodeCombat activity." # email_notifications_summary: "Controls for personalized, automatic email notifications related to your CodeCombat activity."
# email_any_notes: "Any Notifications" email_any_notes: "Bármely értesítés"
# email_any_notes_description: "Disable to stop all activity notification emails." email_any_notes_description: "Minden tevékenységgel kapcsolatos e-mail értesítés letiltása."
# email_recruit_notes: "Job Opportunities" email_recruit_notes: "Álláslehetőségek"
# email_recruit_notes_description: "If you play really well, we may contact you about getting you a (better) job." email_recruit_notes_description: "Ha igazán jól játszol lehet, hogy felveszzük veled a kapcsolatot és megbeszéljük, hogy szerezzünk-e neked egy (jobb) állást."
contributor_emails: "Hozzájárulóknak szóló levelek" contributor_emails: "Hozzájárulóknak szóló levelek"
contribute_prefix: "Folyamatosan keresünk embereket, hogy csatlakozzanak hozzánk. Nézz rá a " contribute_prefix: "Folyamatosan keresünk embereket, hogy csatlakozzanak hozzánk. Nézz rá a "
contribute_page: "segítők oldalára" contribute_page: "segítők oldalára"
@ -181,8 +181,8 @@ module.exports = nativeDescription: "magyar", englishDescription: "Hungarian", t
job_profile: "Munkaköri leírás" job_profile: "Munkaköri leírás"
job_profile_approved: "Munkaköri leírásodat a Codecombat jóváhagyta. Munkaadók mindaddig láthatják, amíg meg nem jelölöd inaktíként, vagy négy hétig,ha addig nem kerül megváltoztatásra." job_profile_approved: "Munkaköri leírásodat a Codecombat jóváhagyta. Munkaadók mindaddig láthatják, amíg meg nem jelölöd inaktíként, vagy négy hétig,ha addig nem kerül megváltoztatásra."
job_profile_explanation: "Szió! Töltsd ki ezt és majd kapcsolatba lépünk veled és keresünk neked egy szoftware fejlesztői állást." job_profile_explanation: "Szió! Töltsd ki ezt és majd kapcsolatba lépünk veled és keresünk neked egy szoftware fejlesztői állást."
# sample_profile: "See a sample profile" sample_profile: "Nézz meg egy mintaprofilt!"
# view_profile: "View Your Profile" view_profile: "Nézd meg a profilodat!"
account_profile: account_profile:
edit_settings: "Beállítások szerkesztése" edit_settings: "Beállítások szerkesztése"

View file

@ -15,10 +15,10 @@ class CocoModel extends Backbone.Model
super() super()
if not @constructor.className if not @constructor.className
console.error("#{@} needs a className set.") console.error("#{@} needs a className set.")
@markToRevert()
@addSchemaDefaults() @addSchemaDefaults()
@on 'sync', @onLoaded, @ @on 'sync', @onLoaded, @
@on 'error', @onError, @ @on 'error', @onError, @
@on 'add', @onLoaded, @
@saveBackup = _.debounce(@saveBackup, 500) @saveBackup = _.debounce(@saveBackup, 500)
type: -> type: ->
@ -38,14 +38,15 @@ class CocoModel extends Backbone.Model
@loaded = true @loaded = true
@loading = false @loading = false
@jqxhr = null @jqxhr = null
@markToRevert()
@loadFromBackup() @loadFromBackup()
getNormalizedURL: -> "#{@urlRoot}/#{@id}" getNormalizedURL: -> "#{@urlRoot}/#{@id}"
set: -> set: ->
inFlux = @loading or not @loaded
@markToRevert() unless inFlux or @_revertAttributes
res = super(arguments...) res = super(arguments...)
@saveBackup() if @saveBackups and @loaded and @hasLocalChanges() @saveBackup() if @saveBackups and (not inFlux) and @hasLocalChanges()
res res
loadFromBackup: -> loadFromBackup: ->
@ -61,22 +62,29 @@ class CocoModel extends Backbone.Model
@backedUp = {} @backedUp = {}
schema: -> return @constructor.schema schema: -> return @constructor.schema
getValidationErrors: ->
errors = tv4.validateMultiple(@attributes, @constructor.schema or {}).errors
return errors if errors?.length
validate: -> validate: ->
result = tv4.validateMultiple(@attributes, @constructor.schema? or {}) errors = @getValidationErrors()
if result.errors?.length if errors?.length
console.log @, "got validate result with errors:", result console.debug "Validation failed for #{@constructor.className}: '#{@get('name') or @}'."
return result.errors unless result.valid for error in errors
console.debug "\t", error.dataPath, ":", error.message
return errors
save: (attrs, options) -> save: (attrs, options) ->
@set 'editPath', document.location.pathname
options ?= {} options ?= {}
options.headers ?= {}
options.headers['X-Current-Path'] = document.location.pathname
success = options.success success = options.success
error = options.error error = options.error
options.success = (model, res) => options.success = (model, res) =>
@trigger "save:success", @ @trigger "save:success", @
success(@, res) if success success(@, res) if success
@markToRevert() @markToRevert() if @_revertAttributes
@clearBackup() @clearBackup()
options.error = (model, res) => options.error = (model, res) =>
error(@, res) if error error(@, res) if error
@ -93,7 +101,7 @@ class CocoModel extends Backbone.Model
@jqxhr @jqxhr
markToRevert: -> markToRevert: ->
return unless @saveBackups console.debug "Saving _revertAttributes for #{@constructor.className}: '#{@get('name')}'"
if @type() is 'ThangType' if @type() is 'ThangType'
@_revertAttributes = _.clone @attributes # No deep clones for these! @_revertAttributes = _.clone @attributes # No deep clones for these!
else else
@ -142,7 +150,6 @@ class CocoModel extends Backbone.Model
#console.log "setting", prop, "to", sch.default, "from sch.default" if sch.default? #console.log "setting", prop, "to", sch.default, "from sch.default" if sch.default?
@set prop, sch.default if sch.default? @set prop, sch.default if sch.default?
if @loaded if @loaded
@markToRevert()
@loadFromBackup() @loadFromBackup()
@isObjectID: (s) -> @isObjectID: (s) ->

View file

@ -52,4 +52,8 @@
.checkbox .checkbox
margin: 10px 10px 0 margin: 10px 10px 0
input input
margin-right: 5px margin-right: 5px
#errors-wrapper
margin-top: 20px
margin-bottom: 0

View file

@ -53,3 +53,7 @@ block modal-body-content
label label
input(id=id + "-version-is-major", name="version-is-major", type="checkbox") input(id=id + "-version-is-major", name="version-is-major", type="checkbox")
span(data-i18n="versions.new_major_version") New Major Version span(data-i18n="versions.new_major_version") New Major Version
#errors-wrapper.alert.alert-danger.hide
strong Validation Error! Save failed.
p.errors

View file

@ -44,26 +44,27 @@ module.exports = class DeltaView extends CocoView
@expandedDeltas = @model.getExpandedDeltaWith(@comparisonModel) @expandedDeltas = @model.getExpandedDeltaWith(@comparisonModel)
else else
@expandedDeltas = @model.getExpandedDelta() @expandedDeltas = @model.getExpandedDelta()
[@expandedDeltas, @skippedDeltas] = @filterDeltas(@expandedDeltas)
@filterExpandedDeltas()
if @headModel if @headModel
@headDeltas = @headModel.getExpandedDelta() @headDeltas = @headModel.getExpandedDelta()
@headDeltas = @filterDeltas(@headDeltas)[0]
@conflicts = deltasLib.getConflicts(@headDeltas, @expandedDeltas) @conflicts = deltasLib.getConflicts(@headDeltas, @expandedDeltas)
filterExpandedDeltas: -> filterDeltas: (deltas) ->
return unless @skipPaths return [deltas, []] unless @skipPaths
for path, i in @skipPaths for path, i in @skipPaths
@skipPaths[i] = [path] if _.isString(path) @skipPaths[i] = [path] if _.isString(path)
newDeltas = [] newDeltas = []
for delta in @expandedDeltas skippedDeltas = []
for delta in deltas
skip = false skip = false
for skipPath in @skipPaths for skipPath in @skipPaths
if _.isEqual _.first(delta.dataPath, skipPath.length), skipPath if _.isEqual _.first(delta.dataPath, skipPath.length), skipPath
skip = true skip = true
break break
newDeltas.push delta unless skip if skip then skippedDeltas.push delta else newDeltas.push delta
@expandedDeltas = newDeltas [newDeltas, skippedDeltas]
getRenderData: -> getRenderData: ->
c = super() c = super()
@ -87,7 +88,7 @@ module.exports = class DeltaView extends CocoView
@expandDetails(deltaEl, deltaData) @expandDetails(deltaEl, deltaData)
expandDetails: (deltaEl, deltaData) -> expandDetails: (deltaEl, deltaData) ->
treemaOptions = { schema: deltaData.schema, readOnly: true } treemaOptions = { schema: deltaData.schema or {}, readOnly: true }
if _.isObject(deltaData.left) and leftEl = deltaEl.find('.old-value') if _.isObject(deltaData.left) and leftEl = deltaEl.find('.old-value')
options = _.defaults {data: deltaData.left}, treemaOptions options = _.defaults {data: deltaData.left}, treemaOptions
@ -109,5 +110,6 @@ module.exports = class DeltaView extends CocoView
getApplicableDelta: -> getApplicableDelta: ->
delta = @model.getDelta() delta = @model.getDelta()
delta = deltasLib.pruneConflictsFromDelta delta, @conflicts if @conflicts delta = deltasLib.pruneConflictsFromDelta delta, @conflicts if @conflicts
delta = deltasLib.pruneExpandedDeltasFromDelta delta, @skippedDeltas if @skippedDeltas
delta delta

View file

@ -53,7 +53,7 @@ module.exports = class LevelSaveView extends SaveVersionModal
commitLevel: -> commitLevel: ->
modelsToSave = [] modelsToSave = []
@showLoading() formsToSave = []
for form in @$el.find('form') for form in @$el.find('form')
# Level form is first, then LevelComponents' forms, then LevelSystems' forms # Level form is first, then LevelComponents' forms, then LevelSystems' forms
fields = {} fields = {}
@ -75,7 +75,19 @@ module.exports = class LevelSaveView extends SaveVersionModal
@level.publish() @level.publish()
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
for model in modelsToSave
if errors = model.getValidationErrors()
messages = ("\t #{error.dataPath}: #{error.message}" for error in errors)
messages = messages.join('<br />')
@$el.find('#errors-wrapper .errors').html(messages)
@$el.find('#errors-wrapper').removeClass('hide')
return
@showLoading()
tuples = _.zip(modelsToSave, formsToSave)
for [newModel, form] in tuples
res = newModel.save() res = newModel.save()
do (newModel, form) => do (newModel, form) =>
res.error => res.error =>

View file

@ -152,32 +152,6 @@ class EventPrereqNode extends TreemaNode.nodeMap.object
class ChannelNode extends TreemaNode.nodeMap.string class ChannelNode extends TreemaNode.nodeMap.string
buildValueForEditing: (valEl) -> buildValueForEditing: (valEl) ->
super(valEl) super(valEl)
valEl.find('input').autocomplete(source: channels, minLength: 0, delay: 0, autoFocus: true) autocompleteValues = ({label: val?.title or key, value: key} for key, val of Backbone.Mediator.channelSchemas)
valEl.find('input').autocomplete(source: autocompleteValues, minLength: 0, delay: 0, autoFocus: true)
valEl valEl
channels = [
"tome:palette-hovered",
"tome:palette-clicked",
"tome:spell-shown",
"end-current-script",
"goal-manager:new-goal-states",
"god:new-world-created",
"help-multiplayer",
"help-next",
"help-overview",
"level-restart-ask",
"level-set-playing",
"level:docs-hidden",
"level:team-set",
"playback:manually-scrubbed",
"sprite:speech-updated",
"surface:coordinates-shown",
"surface:frame-changed",
"surface:sprite-selected",
"world:thang-attacked-when-out-of-range",
"world:thang-collected-item",
"world:thang-died",
"world:thang-left-map",
"world:thang-touched-goal",
"world:won"
]

View file

@ -51,4 +51,5 @@ module.exports = class SettingsTabView extends View
onSettingsChanged: (e) => onSettingsChanged: (e) =>
$('.level-title').text @settingsTreema.data.name $('.level-title').text @settingsTreema.data.name
for key in @editableSettings for key in @editableSettings
continue unless @settingsTreema.data[key] is undefined
@level.set key, @settingsTreema.data[key] @level.set key, @settingsTreema.data[key]

View file

@ -194,7 +194,7 @@ module.exports = class ThangsTabView extends View
return unless @selectedExtantThang and e.thang?.id is @selectedExtantThang?.id return unless @selectedExtantThang and e.thang?.id is @selectedExtantThang?.id
@surface.camera.dragDisabled = true @surface.camera.dragDisabled = true
{stageX, stageY} = e.originalEvent {stageX, stageY} = e.originalEvent
wop = @surface.camera.canvasToWorld x: stageX, y: stageY wop = @surface.camera.screenToWorld x: stageX, y: stageY
wop.z = @selectedExtantThang.depth / 2 wop.z = @selectedExtantThang.depth / 2
@adjustThangPos @selectedExtantSprite, @selectedExtantThang, wop @adjustThangPos @selectedExtantSprite, @selectedExtantThang, wop
[w, h] = [@surface.camera.canvasWidth, @surface.camera.canvasHeight] [w, h] = [@surface.camera.canvasWidth, @surface.camera.canvasHeight]
@ -250,6 +250,7 @@ module.exports = class ThangsTabView extends View
# @thangsTreema.deselectAll() # @thangsTreema.deselectAll()
selectAddThang: (e) => selectAddThang: (e) =>
return if e? and $(e.target).closest('#thang-search').length # Ignore if you're trying to search thangs
return unless e? and $(e.target).closest('#editor-level-thangs-tab-view').length or key.isPressed('esc') return unless e? and $(e.target).closest('#editor-level-thangs-tab-view').length or key.isPressed('esc')
if e then target = $(e.target) else target = @$el.find('.add-thangs-palette') # pretend to click on background if no event if e then target = $(e.target) else target = @$el.find('.add-thangs-palette') # pretend to click on background if no event
return true if target.attr('id') is 'surface' return true if target.attr('id') is 'surface'
@ -258,7 +259,7 @@ module.exports = class ThangsTabView extends View
@$el.find('.add-thangs-palette .add-thang-palette-icon.selected').removeClass('selected') @$el.find('.add-thangs-palette .add-thang-palette-icon.selected').removeClass('selected')
@selectAddThangType(if wasSelected then null else target.attr 'data-thang-type') unless key.alt or key.meta @selectAddThangType(if wasSelected then null else target.attr 'data-thang-type') unless key.alt or key.meta
target.addClass('selected') if @addThangType target.addClass('selected') if @addThangType
false #false # was causing #1099, any reason to keep?
moveAddThangSelection: (direction) -> moveAddThangSelection: (direction) ->
return unless @addThangType return unless @addThangType
@ -319,7 +320,7 @@ module.exports = class ThangsTabView extends View
onSurfaceMouseMoved: (e) -> onSurfaceMouseMoved: (e) ->
return unless @addThangSprite return unless @addThangSprite
wop = @surface.camera.canvasToWorld x: e.x, y: e.y wop = @surface.camera.screenToWorld x: e.x, y: e.y
wop.z = 0.5 wop.z = 0.5
@adjustThangPos @addThangSprite, @addThangSprite.thang, wop @adjustThangPos @addThangSprite, @addThangSprite.thang, wop
null null

View file

@ -8,6 +8,7 @@ module.exports = class PatchModal extends ModalView
template: template template: template
plain: true plain: true
modalWidthPercent: 60 modalWidthPercent: 60
@DOC_SKIP_PATHS = ['_id','version', 'commitMessage', 'parent', 'created', 'slug', 'index', '__v', 'patches']
events: events:
'click #withdraw-button': 'withdrawPatch' 'click #withdraw-button': 'withdrawPatch'
@ -36,14 +37,16 @@ module.exports = class PatchModal extends ModalView
headModel = null headModel = null
if @targetModel.hasWriteAccess() if @targetModel.hasWriteAccess()
headModel = @originalSource.clone(false) headModel = @originalSource.clone(false)
headModel.markToRevert true
headModel.set(@targetModel.attributes) headModel.set(@targetModel.attributes)
headModel.loaded = true headModel.loaded = true
pendingModel = @originalSource.clone(false) pendingModel = @originalSource.clone(false)
pendingModel.markToRevert true
pendingModel.applyDelta(@patch.get('delta')) pendingModel.applyDelta(@patch.get('delta'))
pendingModel.loaded = true pendingModel.loaded = true
@deltaView = new DeltaView({model:pendingModel, headModel:headModel}) @deltaView = new DeltaView({model:pendingModel, headModel:headModel, skipPaths: PatchModal.DOC_SKIP_PATHS})
changeEl = @$el.find('.changes-stub') changeEl = @$el.find('.changes-stub')
@insertSubView(@deltaView, changeEl) @insertSubView(@deltaView, changeEl)
super() super()

View file

@ -2,6 +2,7 @@ ModalView = require 'views/kinds/ModalView'
template = require 'templates/modal/versions' template = require 'templates/modal/versions'
tableTemplate = require 'templates/kinds/table' tableTemplate = require 'templates/kinds/table'
DeltaView = require 'views/editor/delta' DeltaView = require 'views/editor/delta'
PatchModal = require 'views/editor/patch_modal'
class VersionsViewCollection extends Backbone.Collection class VersionsViewCollection extends Backbone.Collection
url: "" url: ""
@ -49,7 +50,7 @@ module.exports = class VersionsModalView extends ModalView
laterVersion = new @model(_id:$(rows[0]).val()) laterVersion = new @model(_id:$(rows[0]).val())
earlierVersion = new @model(_id:$(rows[1]).val()) earlierVersion = new @model(_id:$(rows[1]).val())
@deltaView = new DeltaView({model:earlierVersion, comparisonModel:laterVersion, skipPaths:['_id','version', 'commitMessage', 'parent', 'created', 'slug', 'index']}) @deltaView = new DeltaView({model:earlierVersion, comparisonModel:laterVersion, skipPaths:PatchModal.DOC_SKIP_PATHS})
@insertSubView(@deltaView, deltaEl) @insertSubView(@deltaView, deltaEl)
getRenderData: (context={}) -> getRenderData: (context={}) ->

View file

@ -325,7 +325,8 @@ module.exports = class Handler
newDocument.save (err) => newDocument.save (err) =>
return @sendDatabaseError(res, err) if err return @sendDatabaseError(res, err) if err
@sendSuccess(res, @formatEntity(req, newDocument)) @sendSuccess(res, @formatEntity(req, newDocument))
@notifyWatchersOfChange(req.user, newDocument, req.body.editPath) if @modelClass.schema.is_patchable if @modelClass.schema.is_patchable
@notifyWatchersOfChange(req.user, newDocument, req.headers['x-current-path'])
if major? if major?
parentDocument.makeNewMinorVersion(updatedObject, major, done) parentDocument.makeNewMinorVersion(updatedObject, major, done)