mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-24 08:08:15 -05:00
Merge branch 'master' into feature/bootstrap3
This commit is contained in:
commit
88c8c3896b
49 changed files with 1515 additions and 339 deletions
File diff suppressed because one or more lines are too long
|
@ -1,7 +1,7 @@
|
||||||
CocoClass = require 'lib/CocoClass'
|
CocoClass = require 'lib/CocoClass'
|
||||||
{me, CURRENT_USER_KEY} = require 'lib/auth'
|
{me, CURRENT_USER_KEY} = require 'lib/auth'
|
||||||
{backboneFailure} = require 'lib/errors'
|
{backboneFailure} = require 'lib/errors'
|
||||||
{saveObjectToStorage} = require 'lib/storage'
|
storage = require 'lib/storage'
|
||||||
|
|
||||||
# facebook user object props to
|
# facebook user object props to
|
||||||
userPropsToSave =
|
userPropsToSave =
|
||||||
|
@ -59,7 +59,7 @@ module.exports = FacebookHandler = class FacebookHandler extends CocoClass
|
||||||
error: backboneFailure,
|
error: backboneFailure,
|
||||||
url: "/db/user?facebookID=#{r.id}&facebookAccessToken=#{@authResponse.accessToken}"
|
url: "/db/user?facebookID=#{r.id}&facebookAccessToken=#{@authResponse.accessToken}"
|
||||||
success: (model) ->
|
success: (model) ->
|
||||||
saveObjectToStorage(CURRENT_USER_KEY, model.attributes)
|
storage.save(CURRENT_USER_KEY, model.attributes)
|
||||||
window.location.reload() if model.get('email') isnt oldEmail
|
window.location.reload() if model.get('email') isnt oldEmail
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
CocoClass = require 'lib/CocoClass'
|
CocoClass = require 'lib/CocoClass'
|
||||||
{me, CURRENT_USER_KEY} = require 'lib/auth'
|
{me, CURRENT_USER_KEY} = require 'lib/auth'
|
||||||
{backboneFailure} = require 'lib/errors'
|
{backboneFailure} = require 'lib/errors'
|
||||||
{saveObjectToStorage} = require 'lib/storage'
|
storage = require 'lib/storage'
|
||||||
|
|
||||||
# gplus user object props to
|
# gplus user object props to
|
||||||
userPropsToSave =
|
userPropsToSave =
|
||||||
|
@ -73,7 +73,7 @@ module.exports = GPlusHandler = class GPlusHandler extends CocoClass
|
||||||
error: backboneFailure,
|
error: backboneFailure,
|
||||||
url: "/db/user?gplusID=#{gplusID}&gplusAccessToken=#{@accessToken}"
|
url: "/db/user?gplusID=#{gplusID}&gplusAccessToken=#{@accessToken}"
|
||||||
success: (model) ->
|
success: (model) ->
|
||||||
saveObjectToStorage(CURRENT_USER_KEY, model.attributes)
|
storage.save(CURRENT_USER_KEY, model.attributes)
|
||||||
window.location.reload()
|
window.location.reload()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{backboneFailure, genericFailure} = require 'lib/errors'
|
{backboneFailure, genericFailure} = require 'lib/errors'
|
||||||
User = require 'models/User'
|
User = require 'models/User'
|
||||||
{saveObjectToStorage, loadObjectFromStorage} = require 'lib/storage'
|
storage = require 'lib/storage'
|
||||||
|
|
||||||
module.exports.CURRENT_USER_KEY = CURRENT_USER_KEY = 'whoami'
|
module.exports.CURRENT_USER_KEY = CURRENT_USER_KEY = 'whoami'
|
||||||
BEEN_HERE_BEFORE_KEY = 'beenHereBefore'
|
BEEN_HERE_BEFORE_KEY = 'beenHereBefore'
|
||||||
|
@ -10,7 +10,7 @@ module.exports.createUser = (userObject, failure=backboneFailure) ->
|
||||||
user.save({}, {
|
user.save({}, {
|
||||||
error: failure,
|
error: failure,
|
||||||
success: (model) ->
|
success: (model) ->
|
||||||
saveObjectToStorage(CURRENT_USER_KEY, model)
|
storage.save(CURRENT_USER_KEY, model)
|
||||||
window.location.reload()
|
window.location.reload()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ module.exports.loginUser = (userObject, failure=genericFailure) ->
|
||||||
password:userObject.password
|
password:userObject.password
|
||||||
},
|
},
|
||||||
(model) ->
|
(model) ->
|
||||||
saveObjectToStorage(CURRENT_USER_KEY, model)
|
storage.save(CURRENT_USER_KEY, model)
|
||||||
window.location.reload()
|
window.location.reload()
|
||||||
)
|
)
|
||||||
jqxhr.fail(failure)
|
jqxhr.fail(failure)
|
||||||
|
@ -29,7 +29,7 @@ module.exports.loginUser = (userObject, failure=genericFailure) ->
|
||||||
module.exports.logoutUser = ->
|
module.exports.logoutUser = ->
|
||||||
FB?.logout?()
|
FB?.logout?()
|
||||||
res = $.post('/auth/logout', {}, ->
|
res = $.post('/auth/logout', {}, ->
|
||||||
saveObjectToStorage(CURRENT_USER_KEY, null)
|
storage.save(CURRENT_USER_KEY, null)
|
||||||
window.location.reload()
|
window.location.reload()
|
||||||
)
|
)
|
||||||
res.fail(genericFailure)
|
res.fail(genericFailure)
|
||||||
|
@ -38,7 +38,7 @@ init = ->
|
||||||
# Load the user from local storage, and refresh it from the server.
|
# Load the user from local storage, and refresh it from the server.
|
||||||
# Also refresh and cache the gravatar info.
|
# Also refresh and cache the gravatar info.
|
||||||
|
|
||||||
storedUser = loadObjectFromStorage(CURRENT_USER_KEY)
|
storedUser = storage.load(CURRENT_USER_KEY)
|
||||||
firstTime = not storedUser
|
firstTime = not storedUser
|
||||||
module.exports.me = window.me = new User(storedUser)
|
module.exports.me = window.me = new User(storedUser)
|
||||||
me.url = -> '/auth/whoami'
|
me.url = -> '/auth/whoami'
|
||||||
|
@ -50,14 +50,14 @@ init = ->
|
||||||
# Assign testGroupNumber to returning visitors; new ones in server/handlers/user
|
# Assign testGroupNumber to returning visitors; new ones in server/handlers/user
|
||||||
me.set 'testGroupNumber', Math.floor(Math.random() * 256)
|
me.set 'testGroupNumber', Math.floor(Math.random() * 256)
|
||||||
me.save()
|
me.save()
|
||||||
saveObjectToStorage(CURRENT_USER_KEY, me.attributes)
|
storage.save(CURRENT_USER_KEY, me.attributes)
|
||||||
|
|
||||||
me.loadGravatarProfile() if me.get('email')
|
me.loadGravatarProfile() if me.get('email')
|
||||||
me.on('sync', userSynced)
|
me.on('sync', userSynced)
|
||||||
|
|
||||||
userSynced = (user) ->
|
userSynced = (user) ->
|
||||||
Backbone.Mediator.publish('me:synced', {me:user})
|
Backbone.Mediator.publish('me:synced', {me:user})
|
||||||
saveObjectToStorage(CURRENT_USER_KEY, user)
|
storage.save(CURRENT_USER_KEY, user)
|
||||||
|
|
||||||
init()
|
init()
|
||||||
|
|
||||||
|
@ -71,7 +71,7 @@ Backbone.Mediator.subscribe('level-set-volume', onSetVolume, module.exports)
|
||||||
trackFirstArrival = ->
|
trackFirstArrival = ->
|
||||||
# will have to filter out users who log in with existing accounts separately
|
# will have to filter out users who log in with existing accounts separately
|
||||||
# but can at least not track logouts as first arrivals using local storage
|
# but can at least not track logouts as first arrivals using local storage
|
||||||
beenHereBefore = loadObjectFromStorage(BEEN_HERE_BEFORE_KEY)
|
beenHereBefore = storage.load(BEEN_HERE_BEFORE_KEY)
|
||||||
return if beenHereBefore
|
return if beenHereBefore
|
||||||
window.tracker?.trackEvent 'First Arrived'
|
window.tracker?.trackEvent 'First Arrived'
|
||||||
saveObjectToStorage(BEEN_HERE_BEFORE_KEY, true)
|
storage.save(BEEN_HERE_BEFORE_KEY, true)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
module.exports.loadObjectFromStorage = (key) ->
|
module.exports.load = (key) ->
|
||||||
s = localStorage.getItem(key)
|
s = localStorage.getItem(key)
|
||||||
return null unless s
|
return null unless s
|
||||||
try
|
try
|
||||||
|
@ -8,6 +8,8 @@ module.exports.loadObjectFromStorage = (key) ->
|
||||||
console.warning('error loading from storage', key)
|
console.warning('error loading from storage', key)
|
||||||
return null
|
return null
|
||||||
|
|
||||||
module.exports.saveObjectToStorage = (key, value) ->
|
module.exports.save = (key, value) ->
|
||||||
s = JSON.stringify(value)
|
s = JSON.stringify(value)
|
||||||
localStorage.setItem(key, s)
|
localStorage.setItem(key, s)
|
||||||
|
|
||||||
|
module.exports.remove = (key) -> localStorage.removeItem key
|
|
@ -186,6 +186,7 @@ module.exports = class Camera extends CocoClass
|
||||||
# Target is either just a {x, y} pos or a display object with {x, y} that might change; surface coordinates.
|
# Target is either just a {x, y} pos or a display object with {x, y} that might change; surface coordinates.
|
||||||
time = 0 if @instant
|
time = 0 if @instant
|
||||||
newTarget ?= {x:0, y:0}
|
newTarget ?= {x:0, y:0}
|
||||||
|
newTarget = (@newTarget or @target) if @locked
|
||||||
newZoom = Math.min((Math.max @minZoom, newZoom), MAX_ZOOM)
|
newZoom = Math.min((Math.max @minZoom, newZoom), MAX_ZOOM)
|
||||||
return if @zoom is newZoom and newTarget is newTarget.x and newTarget.y is newTarget.y
|
return if @zoom is newZoom and newTarget is newTarget.x and newTarget.y is newTarget.y
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
module.exports.thangNames = thangNames =
|
module.exports.thangNames = thangNames =
|
||||||
"Soldier": [
|
"Soldier M": [
|
||||||
"William"
|
"William"
|
||||||
"Lucas"
|
"Lucas"
|
||||||
"Marcus"
|
"Marcus"
|
||||||
|
@ -45,16 +45,18 @@ module.exports.thangNames = thangNames =
|
||||||
"Sterling"
|
"Sterling"
|
||||||
"Alistair"
|
"Alistair"
|
||||||
"Remy"
|
"Remy"
|
||||||
"Lana"
|
|
||||||
"Stormy"
|
"Stormy"
|
||||||
"Halle"
|
"Halle"
|
||||||
|
"Sage"
|
||||||
|
]
|
||||||
|
"Soldier F": [
|
||||||
"Sarah"
|
"Sarah"
|
||||||
"Alexandra"
|
"Alexandra"
|
||||||
"Holly"
|
"Holly"
|
||||||
"Trinity"
|
"Trinity"
|
||||||
"Nikita"
|
"Nikita"
|
||||||
"Alana"
|
"Alana"
|
||||||
"Sage"
|
"Lana"
|
||||||
]
|
]
|
||||||
"Peasant": [
|
"Peasant": [
|
||||||
"Yorik"
|
"Yorik"
|
||||||
|
@ -77,7 +79,7 @@ module.exports.thangNames = thangNames =
|
||||||
"Bernadette"
|
"Bernadette"
|
||||||
"Hershell"
|
"Hershell"
|
||||||
]
|
]
|
||||||
"Archer": [
|
"Archer F": [
|
||||||
"Phoebe"
|
"Phoebe"
|
||||||
"Mira"
|
"Mira"
|
||||||
"Agapi"
|
"Agapi"
|
||||||
|
@ -98,13 +100,15 @@ module.exports.thangNames = thangNames =
|
||||||
"Clare"
|
"Clare"
|
||||||
"Rowan"
|
"Rowan"
|
||||||
"Omar"
|
"Omar"
|
||||||
"Brian"
|
|
||||||
"Cole"
|
|
||||||
"Alden"
|
"Alden"
|
||||||
"Cairn"
|
"Cairn"
|
||||||
"Jensen"
|
"Jensen"
|
||||||
]
|
]
|
||||||
"Ogre Munchkin": [
|
"Archer M": [
|
||||||
|
"Brian"
|
||||||
|
"Cole"
|
||||||
|
]
|
||||||
|
"Ogre Munchkin M": [
|
||||||
"Brack"
|
"Brack"
|
||||||
"Gort"
|
"Gort"
|
||||||
"Weeb"
|
"Weeb"
|
||||||
|
@ -128,7 +132,10 @@ module.exports.thangNames = thangNames =
|
||||||
"Snortt"
|
"Snortt"
|
||||||
"Kog"
|
"Kog"
|
||||||
]
|
]
|
||||||
"Ogre": [
|
"Ogre Munchkin F": [
|
||||||
|
|
||||||
|
]
|
||||||
|
"Ogre M": [
|
||||||
"Krogg"
|
"Krogg"
|
||||||
"Dronck"
|
"Dronck"
|
||||||
"Trogdor"
|
"Trogdor"
|
||||||
|
@ -140,6 +147,9 @@ module.exports.thangNames = thangNames =
|
||||||
"Nareng"
|
"Nareng"
|
||||||
"Morthrug"
|
"Morthrug"
|
||||||
"Glonc"
|
"Glonc"
|
||||||
|
]
|
||||||
|
"Ogre F": [
|
||||||
|
|
||||||
]
|
]
|
||||||
"Ogre Brawler": [
|
"Ogre Brawler": [
|
||||||
"Grul'thock"
|
"Grul'thock"
|
||||||
|
|
|
@ -114,4 +114,7 @@ class Rectangle
|
||||||
@deserialize: (o, world, classMap) ->
|
@deserialize: (o, world, classMap) ->
|
||||||
new Rectangle o.x, o.y, o.w, o.h, o.r
|
new Rectangle o.x, o.y, o.w, o.h, o.r
|
||||||
|
|
||||||
|
serializeForAether: -> @serialize()
|
||||||
|
@deserializeFromAether: (o) -> @deserialize o
|
||||||
|
|
||||||
module.exports = Rectangle
|
module.exports = Rectangle
|
||||||
|
|
|
@ -25,7 +25,7 @@ module.exports = class Thang
|
||||||
Thang.lastIDNums ?= {}
|
Thang.lastIDNums ?= {}
|
||||||
names = thangNames[spriteName]
|
names = thangNames[spriteName]
|
||||||
order = @ordering spriteName
|
order = @ordering spriteName
|
||||||
if names
|
if names and names.length
|
||||||
lastIDNum = Thang.lastIDNums[spriteName]
|
lastIDNum = Thang.lastIDNums[spriteName]
|
||||||
idNum = (if lastIDNum? then lastIDNum + 1 else 0)
|
idNum = (if lastIDNum? then lastIDNum + 1 else 0)
|
||||||
Thang.lastIDNums[spriteName] = idNum
|
Thang.lastIDNums[spriteName] = idNum
|
||||||
|
@ -158,6 +158,9 @@ module.exports = class Thang
|
||||||
t[prop] = val
|
t[prop] = val
|
||||||
t
|
t
|
||||||
|
|
||||||
|
serializeForAether: ->
|
||||||
|
{CN: @constructor.className, id: @id}
|
||||||
|
|
||||||
getSpriteOptions: ->
|
getSpriteOptions: ->
|
||||||
colorConfigs = @world?.getTeamColors() or {}
|
colorConfigs = @world?.getTeamColors() or {}
|
||||||
options = {}
|
options = {}
|
||||||
|
|
|
@ -119,4 +119,7 @@ class Vector
|
||||||
@deserialize: (o, world, classMap) ->
|
@deserialize: (o, world, classMap) ->
|
||||||
new Vector o.x, o.y, o.z
|
new Vector o.x, o.y, o.z
|
||||||
|
|
||||||
|
serializeForAether: -> @serialize()
|
||||||
|
@deserializeFromAether: (o) -> @deserialize o
|
||||||
|
|
||||||
module.exports = Vector
|
module.exports = Vector
|
||||||
|
|
|
@ -4,7 +4,7 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis
|
||||||
saving: "Guardando..."
|
saving: "Guardando..."
|
||||||
sending: "Enviando..."
|
sending: "Enviando..."
|
||||||
cancel: "Cancelar"
|
cancel: "Cancelar"
|
||||||
# save: "Save"
|
save: "Guardar"
|
||||||
delay_1_sec: "1 segundo"
|
delay_1_sec: "1 segundo"
|
||||||
delay_3_sec: "3 segundos"
|
delay_3_sec: "3 segundos"
|
||||||
delay_5_sec: "5 segundos"
|
delay_5_sec: "5 segundos"
|
||||||
|
@ -31,15 +31,15 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis
|
||||||
about: "Sobre nosotros"
|
about: "Sobre nosotros"
|
||||||
contact: "Contacta"
|
contact: "Contacta"
|
||||||
twitter_follow: "Síguenos"
|
twitter_follow: "Síguenos"
|
||||||
# employers: "Employers"
|
employers: "Empresas"
|
||||||
|
|
||||||
# versions:
|
versions:
|
||||||
# save_version_title: "Save New Version"
|
save_version_title: "Guardar nueva versión"
|
||||||
# new_major_version: "New Major Version"
|
new_major_version: "Nueva versión principal"
|
||||||
# cla_prefix: "To save changes, first you must agree to our"
|
cla_prefix: "Para guardar los cambios, primero debes aceptar nuestro"
|
||||||
# cla_url: "CLA"
|
cla_url: "CLA"
|
||||||
# cla_suffix: "."
|
cla_suffix: "."
|
||||||
# cla_agree: "I AGREE"
|
cla_agree: "De acuerdo"
|
||||||
|
|
||||||
login:
|
login:
|
||||||
sign_up: "Crear una cuenta"
|
sign_up: "Crear una cuenta"
|
||||||
|
@ -49,10 +49,10 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis
|
||||||
|
|
||||||
recover:
|
recover:
|
||||||
recover_account_title: "recuperar cuenta"
|
recover_account_title: "recuperar cuenta"
|
||||||
# send_password: "Send Recovery Password"
|
send_password: "Enviar recuperación de contraseña"
|
||||||
|
|
||||||
signup:
|
signup:
|
||||||
# create_account_title: "Create Account to Save Progress"
|
create_account_title: "Crea una cuenta para guardar tu progreso"
|
||||||
description: "Es gratis. Solo necesitamos un par de cosas y listo para comenzar!"
|
description: "Es gratis. Solo necesitamos un par de cosas y listo para comenzar!"
|
||||||
email_announcements: "Recibir noticias por correo electrónico"
|
email_announcements: "Recibir noticias por correo electrónico"
|
||||||
coppa: "Soy mayor de 13 o de fuera de los Estados Unidos"
|
coppa: "Soy mayor de 13 o de fuera de los Estados Unidos"
|
||||||
|
@ -70,7 +70,7 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis
|
||||||
play:
|
play:
|
||||||
choose_your_level: "Elige tu nivel"
|
choose_your_level: "Elige tu nivel"
|
||||||
adventurer_prefix: "Puedes elegir cualquier pantalla o charlar en "
|
adventurer_prefix: "Puedes elegir cualquier pantalla o charlar en "
|
||||||
adventurer_forum: "el foro del aventurero"
|
adventurer_forum: "el foro del aventurero "
|
||||||
adventurer_suffix: "sobre ello."
|
adventurer_suffix: "sobre ello."
|
||||||
campaign_beginner: "Campaña de Principiante"
|
campaign_beginner: "Campaña de Principiante"
|
||||||
campaign_beginner_description: "... en la que aprenderás la magia de la programación."
|
campaign_beginner_description: "... en la que aprenderás la magia de la programación."
|
||||||
|
@ -96,14 +96,14 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis
|
||||||
diplomat_suggestion:
|
diplomat_suggestion:
|
||||||
title: "¡Ayuda a traducir CodeCombat!"
|
title: "¡Ayuda a traducir CodeCombat!"
|
||||||
sub_heading: "Necesitamos tus habilidades lingüisticas."
|
sub_heading: "Necesitamos tus habilidades lingüisticas."
|
||||||
pitch_body: "Nosotros desarrollamos CodeCombat en inglés, pero ya tenemos jugadores de todo el mundo. Muchos de ellos quieren jugar en Español porque no hablan inglés, así quesi hablas ambos idiomas, inscríbete como Diplomático y ayuda a traducir la web y todos los niveles de CodeCombat al Español."
|
pitch_body: "Nosotros desarrollamos CodeCombat en inglés, pero ya tenemos jugadores de todo el mundo. Muchos de ellos quieren jugar en español porque no hablan inglés, así que si hablas ambos idiomas, inscríbete como Diplomático y ayuda a traducir la web y todos los niveles de CodeCombat al español."
|
||||||
missing_translations: "Mientras terminamos la traducción al Español, verás en inglés las partes que no estén todavía disponibles."
|
missing_translations: "Mientras terminamos la traducción al español, verás en inglés las partes que no estén todavía disponibles."
|
||||||
learn_more: "Aprende más sobre ser un Diplomático"
|
learn_more: "Aprende más sobre ser un Diplomático"
|
||||||
subscribe_as_diplomat: "Suscríbete como Diplomático"
|
subscribe_as_diplomat: "Suscríbete como Diplomático"
|
||||||
|
|
||||||
# wizard_settings:
|
wizard_settings:
|
||||||
# title: "Wizard Settings"
|
title: "Ajustes del mago"
|
||||||
# customize_avatar: "Customize Your Avatar"
|
customize_avatar: "Personaliza tu Avatar"
|
||||||
|
|
||||||
account_settings:
|
account_settings:
|
||||||
title: "Ajustes de la cuenta"
|
title: "Ajustes de la cuenta"
|
||||||
|
@ -122,7 +122,7 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis
|
||||||
new_password_verify: "Verificar"
|
new_password_verify: "Verificar"
|
||||||
email_subscriptions: "Suscripciones de correo electrónico"
|
email_subscriptions: "Suscripciones de correo electrónico"
|
||||||
email_announcements: "Noticias"
|
email_announcements: "Noticias"
|
||||||
# email_notifications_description: "Get periodic notifications for your account."
|
email_notifications_description: "Recibe notificaciones periódicas para tu cuenta."
|
||||||
email_announcements_description: "Recibe correos electrónicos con las últimas noticias y desarrollos de CodeCombat."
|
email_announcements_description: "Recibe correos electrónicos con las últimas noticias y desarrollos de CodeCombat."
|
||||||
contributor_emails: "Correos para colaboradores"
|
contributor_emails: "Correos para colaboradores"
|
||||||
contribute_prefix: "¡Buscamos gente que se una a nuestro comunidad! Comprueba la "
|
contribute_prefix: "¡Buscamos gente que se una a nuestro comunidad! Comprueba la "
|
||||||
|
@ -139,15 +139,15 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis
|
||||||
# profile_for_suffix: ""
|
# profile_for_suffix: ""
|
||||||
profile: "Perfil"
|
profile: "Perfil"
|
||||||
user_not_found: "No se encontró al usuario. ¿Comprueba la URL?"
|
user_not_found: "No se encontró al usuario. ¿Comprueba la URL?"
|
||||||
gravatar_not_found_mine: "No podemos encontrar el prefil asociado con:"
|
gravatar_not_found_mine: "No podemos encontrar el perfil asociado con:"
|
||||||
# gravatar_not_found_email_suffix: "."
|
# gravatar_not_found_email_suffix: "."
|
||||||
gravatar_signup_prefix: "Suscribete "
|
gravatar_signup_prefix: "¡Suscribete en "
|
||||||
gravatar_signup_suffix: " para ponerte en marcha!"
|
gravatar_signup_suffix: " para ponerte en marcha!"
|
||||||
gravatar_not_found_other: "Vaya, no hay un perfil asociado a la dirección de correo electrónico de esta persona."
|
gravatar_not_found_other: "Vaya, no hay un perfil asociado a la dirección de correo electrónico de esta persona."
|
||||||
gravatar_contact: "Contacto"
|
gravatar_contact: "Contacto"
|
||||||
gravatar_websites: "Paginas web"
|
gravatar_websites: "Paginas web"
|
||||||
gravatar_accounts: "Como se vé en"
|
gravatar_accounts: "Como se vé en"
|
||||||
gravatar_profile_link: "Prefil de Gravatar completo"
|
gravatar_profile_link: "Perfil de Gravatar completo"
|
||||||
|
|
||||||
play_level:
|
play_level:
|
||||||
level_load_error: "No se pudo cargar el nivel."
|
level_load_error: "No se pudo cargar el nivel."
|
||||||
|
@ -191,7 +191,7 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis
|
||||||
tome_select_a_thang: "Selecciona a alguien para "
|
tome_select_a_thang: "Selecciona a alguien para "
|
||||||
tome_available_spells: "Hechizos disponibles"
|
tome_available_spells: "Hechizos disponibles"
|
||||||
hud_continue: "Continuar (pulsa Shift+Space)"
|
hud_continue: "Continuar (pulsa Shift+Space)"
|
||||||
# spell_saved: "Spell Saved"
|
spell_saved: "Hechizo guardado"
|
||||||
|
|
||||||
# admin:
|
# admin:
|
||||||
# av_title: "Admin Views"
|
# av_title: "Admin Views"
|
||||||
|
@ -203,29 +203,29 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis
|
||||||
# u_title: "User List"
|
# u_title: "User List"
|
||||||
# lg_title: "Latest Games"
|
# lg_title: "Latest Games"
|
||||||
|
|
||||||
# editor:
|
editor:
|
||||||
# main_title: "CodeCombat Editors"
|
# main_title: "CodeCombat Editors"
|
||||||
# main_description: "Build your own levels, campaigns, units and educational content. We provide all the tools you need!"
|
main_description: "Construye tus propios niveles, campañas, unidades y contenido educativo. ¡Nosotros te ofrecemos todas las herramientas que necesitas!"
|
||||||
# article_title: "Article Editor"
|
article_title: "Editor de artículos"
|
||||||
# article_description: "Write articles that give players overviews of programming concepts which can be used across a variety of levels and campaigns."
|
# article_description: "Write articles that give players overviews of programming concepts which can be used across a variety of levels and campaigns."
|
||||||
# thang_title: "Thang Editor"
|
# thang_title: "Thang Editor"
|
||||||
# thang_description: "Build units, defining their default logic, graphics and audio. Currently only supports importing Flash exported vector graphics."
|
# thang_description: "Build units, defining their default logic, graphics and audio. Currently only supports importing Flash exported vector graphics."
|
||||||
# level_title: "Level Editor"
|
level_title: "Editor de Niveles"
|
||||||
# level_description: "Includes the tools for scripting, uploading audio, and constructing custom logic to create all sorts of levels. Everything we use ourselves!"
|
# level_description: "Includes the tools for scripting, uploading audio, and constructing custom logic to create all sorts of levels. Everything we use ourselves!"
|
||||||
# security_notice: "Many major features in these editors are not currently enabled by default. As we improve the security of these systems, they will be made generally available. If you'd like to use these features sooner, "
|
# security_notice: "Many major features in these editors are not currently enabled by default. As we improve the security of these systems, they will be made generally available. If you'd like to use these features sooner, "
|
||||||
# contact_us: "contact us!"
|
contact_us: "¡Contacta con nosotros!"
|
||||||
# hipchat_prefix: "You can also find us in our"
|
hipchat_prefix: "También puedes encontrarnos en nuestra"
|
||||||
# hipchat_url: "HipChat room."
|
# hipchat_url: "sala de HipChat."
|
||||||
# level_some_options: "Some Options?"
|
# level_some_options: "Some Options?"
|
||||||
# level_tab_thangs: "Thangs"
|
# level_tab_thangs: "Thangs"
|
||||||
# level_tab_scripts: "Scripts"
|
# level_tab_scripts: "Scripts"
|
||||||
# level_tab_settings: "Settings"
|
level_tab_settings: "Ajustes"
|
||||||
# level_tab_components: "Components"
|
# level_tab_components: "Components"
|
||||||
# level_tab_systems: "Systems"
|
# level_tab_systems: "Systems"
|
||||||
# level_tab_thangs_title: "Current Thangs"
|
# level_tab_thangs_title: "Current Thangs"
|
||||||
# level_tab_thangs_conditions: "Starting Conditions"
|
# level_tab_thangs_conditions: "Starting Conditions"
|
||||||
# level_tab_thangs_add: "Add Thangs"
|
# level_tab_thangs_add: "Add Thangs"
|
||||||
# level_settings_title: "Settings"
|
level_settings_title: "Ajustes"
|
||||||
# level_component_tab_title: "Current Components"
|
# level_component_tab_title: "Current Components"
|
||||||
# level_component_btn_new: "Create New Component"
|
# level_component_btn_new: "Create New Component"
|
||||||
# level_systems_tab_title: "Current Systems"
|
# level_systems_tab_title: "Current Systems"
|
||||||
|
@ -239,86 +239,86 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis
|
||||||
# new_component_title: "Create New Component"
|
# new_component_title: "Create New Component"
|
||||||
# new_component_field_system: "System"
|
# new_component_field_system: "System"
|
||||||
|
|
||||||
# article:
|
article:
|
||||||
# edit_btn_preview: "Preview"
|
edit_btn_preview: "Vista preliminar"
|
||||||
# edit_article_title: "Edit Article"
|
edit_article_title: "Editar artículo"
|
||||||
|
|
||||||
# general:
|
general:
|
||||||
# and: "and"
|
and: "y"
|
||||||
or: "o"
|
or: "o"
|
||||||
# name: "Name"
|
name: "Nombre"
|
||||||
# body: "Body"
|
# body: "Body"
|
||||||
# version: "Version"
|
version: "Versión"
|
||||||
# commit_msg: "Commit Message"
|
# commit_msg: "Commit Message"
|
||||||
# version_history_for: "Version History for: "
|
# version_history_for: "Version History for: "
|
||||||
# results: "Results"
|
results: "Resultados"
|
||||||
# description: "Description"
|
description: "Descripción"
|
||||||
email: "Correo electrónico"
|
email: "Correo electrónico"
|
||||||
message: "Mensaje"
|
message: "Mensaje"
|
||||||
|
|
||||||
# about:
|
about:
|
||||||
# who_is_codecombat: "Who is CodeCombat?"
|
who_is_codecombat: "¿Qué es CodeCombat?"
|
||||||
# why_codecombat: "Why CodeCombat?"
|
why_codecombat: "¿Por qué CodeCombat?"
|
||||||
# who_description_prefix: "together started CodeCombat in 2013. We also created "
|
# who_description_prefix: "together started CodeCombat in 2013. We also created "
|
||||||
# who_description_suffix: "in 2008, growing it to the #1 web and iOS application for learning to write Chinese and Japanese characters."
|
# who_description_suffix: "in 2008, growing it to the #1 web and iOS application for learning to write Chinese and Japanese characters."
|
||||||
# who_description_ending: "Now it's time to teach people to write code."
|
# who_description_ending: "Es hora de empezar a enseñar a la gente a escribir código."
|
||||||
# why_paragraph_1: "When making Skritter, George didn't know how to program and was constantly frustrated by his inability to implement his ideas. Afterwards, he tried learning, but the lessons were too slow. His housemate, wanting to reskill and stop teaching, tried Codecademy, but \"got bored.\" Each week another friend started Codecademy, then dropped off. We realized it was the same problem we'd solved with Skritter: people learning a skill via slow, intensive lessons when what they need is fast, extensive practice. We know how to fix that."
|
# why_paragraph_1: "When making Skritter, George didn't know how to program and was constantly frustrated by his inability to implement his ideas. Afterwards, he tried learning, but the lessons were too slow. His housemate, wanting to reskill and stop teaching, tried Codecademy, but \"got bored.\" Each week another friend started Codecademy, then dropped off. We realized it was the same problem we'd solved with Skritter: people learning a skill via slow, intensive lessons when what they need is fast, extensive practice. We know how to fix that."
|
||||||
# why_paragraph_2: "Need to learn to code? You don't need lessons. You need to write a lot of code and have a great time doing it."
|
# why_paragraph_2: "Need to learn to code? You don't need lessons. You need to write a lot of code and have a great time doing it."
|
||||||
# why_paragraph_3_prefix: "That's what programming is about. It's gotta be fun. Not fun like"
|
why_paragraph_3_prefix: "De eso va la programación. Tiene que ser divertido. No divertido como:"
|
||||||
# why_paragraph_3_italic: "yay a badge"
|
why_paragraph_3_italic: "¡bien una insignia!,"
|
||||||
# why_paragraph_3_center: "but fun like"
|
why_paragraph_3_center: "sino más bien como:"
|
||||||
# why_paragraph_3_italic_caps: "NO MOM I HAVE TO FINISH THE LEVEL!"
|
why_paragraph_3_italic_caps: "¡NO MAMA, TENGO QUE TERMINAR EL NIVEL!"
|
||||||
# why_paragraph_3_suffix: "That's why CodeCombat is a multiplayer game, not a gamified lesson course. We won't stop until you can't stop--but this time, that's a good thing."
|
why_paragraph_3_suffix: "Por eso Codecombat es multijugador, no un curso con lecciones \"gamificadas\" . No pararemos hasta que tú no puedas parar... pero esta vez, eso será buena señal."
|
||||||
# why_paragraph_4: "If you're going to get addicted to some game, get addicted to this one and become one of the wizards of the tech age."
|
why_paragraph_4: "Si vas a engancharte a algún juego, engánchate a este y conviértete en uno de los magos de la era tecnológica."
|
||||||
# why_ending: "And hey, it's free. "
|
why_ending: "Y, oye, es gratis. "
|
||||||
# why_ending_url: "Start wizarding now!"
|
why_ending_url: "Comienza a hacer magia ¡ya!"
|
||||||
# george_description: "CEO, business guy, web designer, game designer, and champion of beginning programmers everywhere."
|
# george_description: "CEO, business guy, web designer, game designer, and champion of beginning programmers everywhere."
|
||||||
# scott_description: "Programmer extraordinaire, software architect, kitchen wizard, and master of finances. Scott is the reasonable one."
|
# scott_description: "Programmer extraordinaire, software architect, kitchen wizard, and master of finances. Scott is the reasonable one."
|
||||||
# nick_description: "Programming wizard, eccentric motivation mage, and upside-down experimenter. Nick can do anything and chooses to build CodeCombat."
|
# nick_description: "Programming wizard, eccentric motivation mage, and upside-down experimenter. Nick can do anything and chooses to build CodeCombat."
|
||||||
# jeremy_description: "Customer support mage, usability tester, and community organizer; you've probably already spoken with Jeremy."
|
# jeremy_description: "Customer support mage, usability tester, and community organizer; you've probably already spoken with Jeremy."
|
||||||
# michael_description: "Programmer, sys-admin, and undergrad technical wunderkind, Michael is the person keeping our servers online."
|
# michael_description: "Programmer, sys-admin, and undergrad technical wunderkind, Michael is the person keeping our servers online."
|
||||||
|
|
||||||
# legal:
|
legal:
|
||||||
# page_title: "Legal"
|
page_title: "Legal"
|
||||||
# opensource_intro: "CodeCombat is free to play and completely open source."
|
opensource_intro: "CodeCombat es gratis y totalmente open source."
|
||||||
# opensource_description_prefix: "Check out "
|
opensource_description_prefix: "Echa un vistazo a "
|
||||||
# github_url: "our GitHub"
|
github_url: "nuestro GitHub"
|
||||||
# opensource_description_center: "and help out if you like! CodeCombat is built on dozens of open source projects, and we love them. See "
|
opensource_description_center: "y ayúdanos si quieres. CodeCombat está desarrollado sobre docenas de proyectos open source, y nos encantana. Mira "
|
||||||
# archmage_wiki_url: "our Archmage wiki"
|
archmage_wiki_url: "nuestra wiki del Archimago"
|
||||||
# opensource_description_suffix: "for a list of the software that makes this game possible."
|
opensource_description_suffix: "para encontrar una lista del software que hace este juego posible."
|
||||||
# practices_title: "Respectful Best Practices"
|
practices_title: "Prácticas respetuosas"
|
||||||
# practices_description: "These are our promises to you, the player, in slightly less legalese."
|
practices_description: "Esto es lo que te prometemos a ti, el jugador, sin usar mucha jerga legal."
|
||||||
# privacy_title: "Privacy"
|
privacy_title: "Privacidad"
|
||||||
# privacy_description: "We will not sell any of your personal information. We intend to make money through recruitment eventually, but rest assured we will not distribute your personal information to interested companies without your explicit consent."
|
privacy_description: "No venderemos tu información personal. Tenemos la intención de hacer dinero a través de la contratación con el tiempo, pero puedes estar seguro que no vamos a distribuir tu información personal a las empresas interesadas sin tu consentimiento expreso."
|
||||||
# security_title: "Security"
|
security_title: "Seguridad"
|
||||||
# security_description: "We strive to keep your personal information safe. As an open source project, our site is freely open to anyone to review and improve our security systems."
|
security_description: "Nos esforzamos por mantener segura tu información personal. Como proyecto de código abierto, nuestro sitio está abierto a cualquiera que quiera revisarlo y mejorar nuestros sistemas de seguridad."
|
||||||
# email_title: "Email"
|
email_title: "Correo electrónico"
|
||||||
# email_description_prefix: "We will not inundate you with spam. Through"
|
email_description_prefix: "No te inundaremos con spam. Mediante"
|
||||||
# email_settings_url: "your email settings"
|
email_settings_url: "tus ajustes de correo electrónico"
|
||||||
# email_description_suffix: "or through links in the emails we send, you can change your preferences and easily unsubscribe at any time."
|
email_description_suffix: "o a través de los enlaces en los correos que te enviemos, puedes cambiar tus preferencias y darte de baja fácilmente en cualquier momento."
|
||||||
# cost_title: "Cost"
|
cost_title: "Precio"
|
||||||
# cost_description: "Currently, CodeCombat is 100% free! One of our main goals is to keep it that way, so that as many people can play as possible, regardless of place in life. If the sky darkens, we might have to charge subscriptions or for some content, but we'd rather not. With any luck, we'll be able to sustain the company with:"
|
cost_description: "Actualmente, ¡CodeCombat es 100% gratis! Uno de nuestros principales objetivos es mantenerlo así, de forma que el mayor número posible de gente pueda jugar, independientemente de sus posibilidades económicas. Si las cosas se tuercen, quizás tengamos que cobrar suscripciones o por algún contenido, pero preferimos no hacerlo. Con un poco de suerte, podremos mantener la empresa con: "
|
||||||
# recruitment_title: "Recruitment"
|
recruitment_title: "Contratación"
|
||||||
# recruitment_description_prefix: "Here on CodeCombat, you're going to become a powerful wizard–not just in the game, but also in real life."
|
recruitment_description_prefix: "En CodeCombat, te vas a convertir en un poderoso mago no solo en el juego, también en el mundo real."
|
||||||
# url_hire_programmers: "No one can hire programmers fast enough"
|
url_hire_programmers: "Nadie puede contratar programadores con la suficiente rapidez"
|
||||||
# recruitment_description_suffix: "so once you've sharpened your skills and if you agree, we will demo your best coding accomplishments to the thousands of employers who are drooling for the chance to hire you. They pay us a little, they pay you"
|
recruitment_description_suffix: "así que una vez que hayas afilado tus habilidades y si estás de acuerdo, mostraremos tus mejores logros en programación a los miles de empresas que están deseando tener la oportunidad de contratarte. Ellos nos pagan un poco y ellos te pagan a ti"
|
||||||
# recruitment_description_italic: "a lot"
|
recruitment_description_italic: "un montón."
|
||||||
# recruitment_description_ending: "the site remains free and everybody's happy. That's the plan."
|
recruitment_description_ending: "La web permanece gratuita y todo el mundo es feliz. Ese es el plan."
|
||||||
# copyrights_title: "Copyrights and Licenses"
|
copyrights_title: "Copyrights y Licencias"
|
||||||
# contributor_title: "Contributor License Agreement"
|
contributor_title: "Acuerdo de Licencia del Colaborador"
|
||||||
# contributor_description_prefix: "All contributions, both on the site and on our GitHub repository, are subject to our"
|
contributor_description_prefix: "Todas las colaboraciones, tanto en la web como en nuestro repositorio de GitHub, están sujetas a nuestro"
|
||||||
# cla_url: "CLA"
|
cla_url: "CLA"
|
||||||
# contributor_description_suffix: "to which you should agree before contributing."
|
contributor_description_suffix: "con el que deberás estar de acuerdo antes de colaborar."
|
||||||
# code_title: "Code - MIT"
|
# code_title: "Code - MIT"
|
||||||
# code_description_prefix: "All code owned by CodeCombat or hosted on codecombat.com, both in the GitHub repository or in the codecombat.com database, is licensed under the"
|
# code_description_prefix: "All code owned by CodeCombat or hosted on codecombat.com, both in the GitHub repository or in the codecombat.com database, is licensed under the"
|
||||||
# mit_license_url: "MIT license"
|
mit_license_url: "Licencia MIT"
|
||||||
# code_description_suffix: "This includes all code in Systems and Components that are made available by CodeCombat for the purpose of creating levels."
|
# code_description_suffix: "This includes all code in Systems and Components that are made available by CodeCombat for the purpose of creating levels."
|
||||||
# art_title: "Art/Music - Creative Commons "
|
art_title: "Arte/Música - Creative Commons "
|
||||||
# art_description_prefix: "All common content is available under the"
|
art_description_prefix: "Todo el contenido común está disponible bajo la"
|
||||||
# cc_license_url: "Creative Commons Attribution 4.0 International License"
|
# cc_license_url: "Creative Commons Attribution 4.0 International License"
|
||||||
# art_description_suffix: "Common content is anything made generally available by CodeCombat for the purpose of creating Levels. This includes:"
|
# art_description_suffix: "Common content is anything made generally available by CodeCombat for the purpose of creating Levels. This includes:"
|
||||||
# art_music: "Music"
|
art_music: "Música"
|
||||||
# art_sound: "Sound"
|
art_sound: "Sonido"
|
||||||
# art_artwork: "Artwork"
|
# art_artwork: "Artwork"
|
||||||
# art_sprites: "Sprites"
|
# art_sprites: "Sprites"
|
||||||
# art_other: "Any and all other non-code creative works that are made available when creating Levels."
|
# art_other: "Any and all other non-code creative works that are made available when creating Levels."
|
||||||
|
@ -339,8 +339,8 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis
|
||||||
# nutshell_description: "Any resources we provide in the Level Editor are free to use as you like for creating Levels. But we reserve the right to restrict distribution of the Levels themselves (that are created on codecombat.com) so that they may be charged for in the future, if that's what ends up happening."
|
# nutshell_description: "Any resources we provide in the Level Editor are free to use as you like for creating Levels. But we reserve the right to restrict distribution of the Levels themselves (that are created on codecombat.com) so that they may be charged for in the future, if that's what ends up happening."
|
||||||
# canonical: "The English version of this document is the definitive, canonical version. If there are any discrepencies between translations, the English document takes precedence."
|
# canonical: "The English version of this document is the definitive, canonical version. If there are any discrepencies between translations, the English document takes precedence."
|
||||||
|
|
||||||
# contribute:
|
contribute:
|
||||||
# page_title: "Contributing"
|
page_title: "Colaborar"
|
||||||
# character_classes_title: "Character Classes"
|
# character_classes_title: "Character Classes"
|
||||||
# introduction_desc_intro: "We have high hopes for CodeCombat."
|
# introduction_desc_intro: "We have high hopes for CodeCombat."
|
||||||
# introduction_desc_pref: "We want to be where programmers of all stripes come to learn and play together, introduce others to the wonderful world of coding, and reflect the best parts of the community. We can't and don't want to do that alone; what makes projects like GitHub, Stack Overflow and Linux great are the people who use them and build on them. To that end, "
|
# introduction_desc_pref: "We want to be where programmers of all stripes come to learn and play together, introduce others to the wonderful world of coding, and reflect the best parts of the community. We can't and don't want to do that alone; what makes projects like GitHub, Stack Overflow and Linux great are the people who use them and build on them. To that end, "
|
||||||
|
@ -424,8 +424,8 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis
|
||||||
# translating_diplomats: "Our Translating Diplomats:"
|
# translating_diplomats: "Our Translating Diplomats:"
|
||||||
# helpful_ambassadors: "Our Helpful Ambassadors:"
|
# helpful_ambassadors: "Our Helpful Ambassadors:"
|
||||||
|
|
||||||
# classes:
|
classes:
|
||||||
# archmage_title: "Archmage"
|
archmage_title: "Archimago"
|
||||||
# archmage_title_description: "(Coder)"
|
# archmage_title_description: "(Coder)"
|
||||||
# artisan_title: "Artisan"
|
# artisan_title: "Artisan"
|
||||||
# artisan_title_description: "(Level Builder)"
|
# artisan_title_description: "(Level Builder)"
|
||||||
|
|
|
@ -205,46 +205,46 @@ module.exports = nativeDescription: "magyar", englishDescription: "Hungarian", t
|
||||||
|
|
||||||
editor:
|
editor:
|
||||||
main_title: "CodeCombat szerkesztők"
|
main_title: "CodeCombat szerkesztők"
|
||||||
main_description: "KLészíts saját pályákat, hadjáratokat, egységeket és oktatési célú tartalmakat. Mi megadunk hozzá minden eszközt amire csak szükséged lehet!"
|
main_description: "Készíts saját pályákat, hadjáratokat, egységeket és oktatési célú tartalmakat. Mi megadunk hozzá minden eszközt amire csak szükséged lehet!"
|
||||||
# article_title: "Article Editor"
|
article_title: "Cikk szerkesztő"
|
||||||
# article_description: "Write articles that give players overviews of programming concepts which can be used across a variety of levels and campaigns."
|
article_description: "Írhatsz cikkeket, hogy átfogó képet adhass olyan programozási szemléletekről, melyeket a különböző pályákon és küldetések során felhasználhatnak."
|
||||||
# thang_title: "Thang Editor"
|
thang_title: "Eszköz szerkesztő"
|
||||||
# thang_description: "Build units, defining their default logic, graphics and audio. Currently only supports importing Flash exported vector graphics."
|
thang_description: "Építs egységeket, határozd meg az működésüket, kinézetüket és hangjukat. Jelenleg csak a Flash-ből exportált vektorgrafika támogatott."
|
||||||
# level_title: "Level Editor"
|
level_title: "Pálya szerkesztő"
|
||||||
# level_description: "Includes the tools for scripting, uploading audio, and constructing custom logic to create all sorts of levels. Everything we use ourselves!"
|
level_description: "Mindent magába foglal, ami kódolás, hangok feltöltése, és a pályák teljesen egyedi felépítése. Minden, amit mi használunk!"
|
||||||
# security_notice: "Many major features in these editors are not currently enabled by default. As we improve the security of these systems, they will be made generally available. If you'd like to use these features sooner, "
|
security_notice: "Számos főbb funkció ezekben a szerkesztőkben még nincs engedélyezve alapesetben. Amint a rendszer biztonságát növelni tudjuk, elérhetővé teszzük ezeket. Ha a későbbiekben használni szeretnéf ezeket a funkciókat, "
|
||||||
# contact_us: "contact us!"
|
contact_us: "lépj kapcsolatba velünk!"
|
||||||
# hipchat_prefix: "You can also find us in our"
|
hipchat_prefix: "Megtalálhatsz bennünket a "
|
||||||
# hipchat_url: "HipChat room."
|
hipchat_url: "HipChat szobában."
|
||||||
# level_some_options: "Some Options?"
|
level_some_options: "Néhány beállítás?"
|
||||||
# level_tab_thangs: "Thangs"
|
level_tab_thangs: "Eszközök"
|
||||||
# level_tab_scripts: "Scripts"
|
level_tab_scripts: "Kódok"
|
||||||
# level_tab_settings: "Settings"
|
level_tab_settings: "Beállítások"
|
||||||
# level_tab_components: "Components"
|
level_tab_components: "Komponensek"
|
||||||
# level_tab_systems: "Systems"
|
level_tab_systems: "Rendszerek"
|
||||||
# level_tab_thangs_title: "Current Thangs"
|
level_tab_thangs_title: "Jelenlegi eszközök"
|
||||||
# level_tab_thangs_conditions: "Starting Conditions"
|
level_tab_thangs_conditions: "Kezdő feltételek"
|
||||||
# level_tab_thangs_add: "Add Thangs"
|
level_tab_thangs_add: "Eszköz hozzáadása"
|
||||||
# level_settings_title: "Settings"
|
level_settings_title: "Beállítások"
|
||||||
# level_component_tab_title: "Current Components"
|
level_component_tab_title: "Jelenlegi komponensek"
|
||||||
# level_component_btn_new: "Create New Component"
|
level_component_btn_new: "Új komponens készítése"
|
||||||
# level_systems_tab_title: "Current Systems"
|
level_systems_tab_title: "Jelenlegi rendszerek"
|
||||||
# level_systems_btn_new: "Create New System"
|
level_systems_btn_new: "Új rendszer készítése"
|
||||||
# level_systems_btn_add: "Add System"
|
level_systems_btn_add: "Rendszer hozzáadása"
|
||||||
# level_components_title: "Back to All Thangs"
|
level_components_title: "Vissza az összes eszközhöz"
|
||||||
# level_components_type: "Type"
|
level_components_type: "Típus"
|
||||||
# level_component_edit_title: "Edit Component"
|
level_component_edit_title: "Komponens szerkesztése"
|
||||||
# level_system_edit_title: "Edit System"
|
level_system_edit_title: "Rendszer szerkesztése"
|
||||||
# create_system_title: "Create New System"
|
create_system_title: "Új rendszer készítése"
|
||||||
# new_component_title: "Create New Component"
|
new_component_title: "Új komponens készítése"
|
||||||
# new_component_field_system: "System"
|
new_component_field_system: "Rendszer"
|
||||||
|
|
||||||
# article:
|
article:
|
||||||
# edit_btn_preview: "Preview"
|
edit_btn_preview: "Előnézet"
|
||||||
# edit_article_title: "Edit Article"
|
edit_article_title: "Cikk szerkesztése"
|
||||||
|
|
||||||
general:
|
general:
|
||||||
# and: "and"
|
and: "és"
|
||||||
or: "vagy "
|
or: "vagy "
|
||||||
name: "Név"
|
name: "Név"
|
||||||
# body: "Body"
|
# body: "Body"
|
||||||
|
|
|
@ -3,3 +3,4 @@ CocoModel = require('./CocoModel')
|
||||||
module.exports = class Article extends CocoModel
|
module.exports = class Article extends CocoModel
|
||||||
@className: "Article"
|
@className: "Article"
|
||||||
urlRoot: "/db/article"
|
urlRoot: "/db/article"
|
||||||
|
saveBackups: true
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
storage = require 'lib/storage'
|
||||||
|
|
||||||
class CocoSchema extends Backbone.Model
|
class CocoSchema extends Backbone.Model
|
||||||
constructor: (path, args...) ->
|
constructor: (path, args...) ->
|
||||||
super(args...)
|
super(args...)
|
||||||
|
@ -9,6 +11,7 @@ class CocoModel extends Backbone.Model
|
||||||
idAttribute: "_id"
|
idAttribute: "_id"
|
||||||
loaded: false
|
loaded: false
|
||||||
loading: false
|
loading: false
|
||||||
|
saveBackups: false
|
||||||
@schema: null
|
@schema: null
|
||||||
|
|
||||||
initialize: ->
|
initialize: ->
|
||||||
|
@ -20,15 +23,32 @@ class CocoModel extends Backbone.Model
|
||||||
@addSchemaDefaults()
|
@addSchemaDefaults()
|
||||||
else
|
else
|
||||||
@loadSchema()
|
@loadSchema()
|
||||||
@once 'sync', @onLoaded
|
@once 'sync', @onLoaded, @
|
||||||
|
@saveBackup = _.debounce(@saveBackup, 500)
|
||||||
|
|
||||||
type: ->
|
type: ->
|
||||||
@constructor.className
|
@constructor.className
|
||||||
|
|
||||||
onLoaded: =>
|
onLoaded: ->
|
||||||
@loaded = true
|
@loaded = true
|
||||||
@loading = false
|
@loading = false
|
||||||
@markToRevert()
|
@markToRevert()
|
||||||
|
if @saveBackups
|
||||||
|
existing = storage.load @id
|
||||||
|
if existing
|
||||||
|
@set(existing, {silent:true})
|
||||||
|
CocoModel.backedUp[@id] = @
|
||||||
|
|
||||||
|
set: ->
|
||||||
|
res = super(arguments...)
|
||||||
|
@saveBackup() if @saveBackups and @loaded
|
||||||
|
res
|
||||||
|
|
||||||
|
saveBackup: ->
|
||||||
|
storage.save(@id, @attributes)
|
||||||
|
CocoModel.backedUp[@id] = @
|
||||||
|
|
||||||
|
@backedUp = {}
|
||||||
|
|
||||||
loadSchema: ->
|
loadSchema: ->
|
||||||
unless @constructor.schema
|
unless @constructor.schema
|
||||||
|
@ -38,7 +58,6 @@ class CocoModel extends Backbone.Model
|
||||||
@constructor.schema.on 'sync', =>
|
@constructor.schema.on 'sync', =>
|
||||||
@constructor.schema.loaded = true
|
@constructor.schema.loaded = true
|
||||||
@addSchemaDefaults()
|
@addSchemaDefaults()
|
||||||
@markToRevert()
|
|
||||||
@trigger 'schema-loaded'
|
@trigger 'schema-loaded'
|
||||||
|
|
||||||
@hasSchema: -> return @schema?.loaded
|
@hasSchema: -> return @schema?.loaded
|
||||||
|
@ -57,6 +76,7 @@ class CocoModel extends Backbone.Model
|
||||||
@trigger "save:success", @
|
@trigger "save:success", @
|
||||||
success(@, resp) if success
|
success(@, resp) if success
|
||||||
@markToRevert()
|
@markToRevert()
|
||||||
|
@clearBackup()
|
||||||
@trigger "save", @
|
@trigger "save", @
|
||||||
return super attrs, options
|
return super attrs, options
|
||||||
|
|
||||||
|
@ -69,6 +89,10 @@ class CocoModel extends Backbone.Model
|
||||||
|
|
||||||
revert: ->
|
revert: ->
|
||||||
@set(@_revertAttributes, {silent: true}) if @_revertAttributes
|
@set(@_revertAttributes, {silent: true}) if @_revertAttributes
|
||||||
|
@clearBackup()
|
||||||
|
|
||||||
|
clearBackup: ->
|
||||||
|
storage.remove @id
|
||||||
|
|
||||||
hasLocalChanges: ->
|
hasLocalChanges: ->
|
||||||
not _.isEqual @attributes, @_revertAttributes
|
not _.isEqual @attributes, @_revertAttributes
|
||||||
|
|
|
@ -6,6 +6,7 @@ class SuperModel
|
||||||
|
|
||||||
populateModel: (model) ->
|
populateModel: (model) ->
|
||||||
@mustPopulate = model
|
@mustPopulate = model
|
||||||
|
model.saveBackups = @shouldSaveBackups(model)
|
||||||
model.fetch() unless model.loaded or model.loading
|
model.fetch() unless model.loaded or model.loading
|
||||||
model.on('sync', @modelLoaded) unless model.loaded
|
model.on('sync', @modelLoaded) unless model.loaded
|
||||||
model.once('error', @modelErrored) unless model.loaded
|
model.once('error', @modelErrored) unless model.loaded
|
||||||
|
@ -13,7 +14,9 @@ class SuperModel
|
||||||
@models[url] = model unless @models[url]?
|
@models[url] = model unless @models[url]?
|
||||||
@modelLoaded(model) if model.loaded
|
@modelLoaded(model) if model.loaded
|
||||||
|
|
||||||
shouldPopulate: (url) -> return true # replace or overwrite
|
# replace or overwrite
|
||||||
|
shouldPopulate: (url) -> return true
|
||||||
|
shouldSaveBackups: (model) -> return false
|
||||||
|
|
||||||
modelErrored: (model) =>
|
modelErrored: (model) =>
|
||||||
@trigger 'error'
|
@trigger 'error'
|
||||||
|
@ -25,6 +28,7 @@ class SuperModel
|
||||||
refs = [] unless @mustPopulate is model or @shouldPopulate(model)
|
refs = [] unless @mustPopulate is model or @shouldPopulate(model)
|
||||||
# console.log 'Loaded', model.get('name')
|
# console.log 'Loaded', model.get('name')
|
||||||
for ref, i in refs
|
for ref, i in refs
|
||||||
|
ref.saveBackups = @shouldSaveBackups(ref)
|
||||||
refURL = ref.url()
|
refURL = ref.url()
|
||||||
continue if @models[refURL]
|
continue if @models[refURL]
|
||||||
@models[refURL] = ref
|
@models[refURL] = ref
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#editor-thang-type-edit-view
|
#editor-thang-type-edit-view
|
||||||
#save-button
|
#save-button, #revert-button
|
||||||
float: right
|
float: right
|
||||||
margin-right: 20px
|
margin-right: 20px
|
||||||
|
|
||||||
|
|
3
app/styles/modal/revert.sass
Normal file
3
app/styles/modal/revert.sass
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
#revert-modal
|
||||||
|
table
|
||||||
|
width: 100%
|
|
@ -31,13 +31,13 @@
|
||||||
|
|
||||||
#cast-button-view
|
#cast-button-view
|
||||||
display: none
|
display: none
|
||||||
|
position: absolute
|
||||||
|
width: 35%
|
||||||
|
|
||||||
.cast-button-group
|
.cast-button-group
|
||||||
position: absolute
|
|
||||||
top: 55px
|
|
||||||
left: 20px
|
|
||||||
z-index: 2
|
z-index: 2
|
||||||
@include opacity(77)
|
@include opacity(77)
|
||||||
|
width: 100%
|
||||||
|
|
||||||
.button-progress-overlay
|
.button-progress-overlay
|
||||||
position: absolute
|
position: absolute
|
||||||
|
@ -75,7 +75,8 @@
|
||||||
padding: 3px 10px
|
padding: 3px 10px
|
||||||
|
|
||||||
.cast-button
|
.cast-button
|
||||||
width: 90px
|
width: 100%
|
||||||
|
height: 29px
|
||||||
|
|
||||||
.autocast-delays
|
.autocast-delays
|
||||||
min-width: 0
|
min-width: 0
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
.problem-alert
|
.problem-alert
|
||||||
z-index: 10
|
z-index: 10
|
||||||
position: absolute
|
position: absolute
|
||||||
bottom: -70px
|
bottom: -110px
|
||||||
left: 10px
|
left: 10px
|
||||||
right: 10px
|
right: 10px
|
||||||
background: transparent url(/images/level/code_editor_error_background.png) no-repeat
|
background: transparent url(/images/level/code_editor_error_background.png) no-repeat
|
||||||
|
|
|
@ -21,19 +21,26 @@
|
||||||
|
|
||||||
.save-status
|
.save-status
|
||||||
display: none
|
display: none
|
||||||
position: relative
|
position: absolute
|
||||||
padding-top: 2px
|
bottom: 2%
|
||||||
padding-left: 10px
|
left: 1%
|
||||||
|
|
||||||
.firepad
|
.firepad
|
||||||
width: 100%
|
|
||||||
height: 100%
|
|
||||||
@include box-sizing(border-box)
|
@include box-sizing(border-box)
|
||||||
|
// When Firepad is active, it wraps .ace_editor in .firepad.
|
||||||
|
width: 98%
|
||||||
|
height: 83%
|
||||||
|
height: -webkit-calc(100% - 60px - 40px)
|
||||||
|
height: calc(100% - 60px - 40px)
|
||||||
|
|
||||||
|
.ace_editor
|
||||||
|
width: 100%
|
||||||
|
height: 100%
|
||||||
|
|
||||||
.ace_editor
|
.ace_editor
|
||||||
@include box-sizing(border-box)
|
@include box-sizing(border-box)
|
||||||
margin-top: 40px
|
// When Firepad isn't active, .ace_editor needs the width/height set itself.
|
||||||
width: 100%
|
width: 98%
|
||||||
height: 83%
|
height: 83%
|
||||||
height: -webkit-calc(100% - 60px - 40px)
|
height: -webkit-calc(100% - 60px - 40px)
|
||||||
height: calc(100% - 60px - 40px)
|
height: calc(100% - 60px - 40px)
|
||||||
|
@ -61,9 +68,9 @@
|
||||||
.executing, .executed, .problem-marker-info, .problem-marker-warning, .problem-marker-error
|
.executing, .executed, .problem-marker-info, .problem-marker-warning, .problem-marker-error
|
||||||
position: absolute
|
position: absolute
|
||||||
.executing
|
.executing
|
||||||
background-color: rgba(216, 255, 255, 0.55)
|
background-color: rgba(216, 255, 255, 0.85)
|
||||||
.executed
|
.executed
|
||||||
background-color: rgba(216, 255, 255, 0.25)
|
background-color: rgba(245, 255, 6, 0.18)
|
||||||
.problem-marker-info
|
.problem-marker-info
|
||||||
background-color: rgba(96, 63, 84, 0.25)
|
background-color: rgba(96, 63, 84, 0.25)
|
||||||
.problem-marker-warning
|
.problem-marker-warning
|
||||||
|
@ -79,7 +86,7 @@
|
||||||
.ace_marker-layer
|
.ace_marker-layer
|
||||||
.ace_bracket
|
.ace_bracket
|
||||||
// Override faint gray
|
// Override faint gray
|
||||||
border-color: #8FF
|
border-color: #BFF
|
||||||
|
|
||||||
.ace_identifier
|
.ace_identifier
|
||||||
background-color: rgba(255, 128, 128, 0.15)
|
border-bottom: 1px dotted rgba(255, 128, 128, 0.45)
|
||||||
|
|
|
@ -1,54 +1,82 @@
|
||||||
@import "../../../bootstrap/mixins"
|
@import "../../../bootstrap/mixins"
|
||||||
|
|
||||||
.spell-toolbar-view
|
.spell-toolbar-view
|
||||||
position: absolute
|
position: relative
|
||||||
z-index: 2
|
|
||||||
top: 2px
|
|
||||||
left: 5px
|
|
||||||
box-sizing: border-box
|
box-sizing: border-box
|
||||||
padding-left: 150px
|
margin: 4px 1%
|
||||||
height: 36px
|
height: 45px
|
||||||
width: 95%
|
width: 97%
|
||||||
width: -webkit-calc(95% - 5px)
|
//background-color: rgba(100, 45, 210, 0.15)
|
||||||
width: calc(95% - 5px)
|
|
||||||
background-color: rgba(100, 45, 210, 0.05)
|
|
||||||
|
|
||||||
.spell-progress
|
.flow
|
||||||
position: relative
|
&:hover .spell-progress
|
||||||
height: 100%
|
opacity: 1
|
||||||
width: 50%
|
|
||||||
display: inline-block
|
|
||||||
|
|
||||||
.progress
|
.spell-progress
|
||||||
position: absolute
|
position: absolute
|
||||||
left: 0px
|
height: 100%
|
||||||
top: 8px
|
width: 40%
|
||||||
bottom: 0px
|
left: 45%
|
||||||
width: 100%
|
display: inline-block
|
||||||
cursor: pointer
|
cursor: pointer
|
||||||
overflow: visible
|
box-sizing: border-box
|
||||||
|
opacity: 0.25
|
||||||
|
|
||||||
.bar
|
.progress
|
||||||
@include transition(width .0s linear)
|
position: absolute
|
||||||
position: relative
|
left: 0px
|
||||||
|
top: 12.5px
|
||||||
|
bottom: 0px
|
||||||
|
width: 100%
|
||||||
|
height: 4px
|
||||||
|
overflow: visible
|
||||||
pointer-events: none
|
pointer-events: none
|
||||||
background-color: #67A4C8
|
|
||||||
width: 50%
|
|
||||||
|
|
||||||
.scrubber-handle
|
.bar
|
||||||
position: absolute
|
@include transition(width .0s linear)
|
||||||
|
position: relative
|
||||||
|
pointer-events: none
|
||||||
|
background: linear-gradient(#2c3e5f, #2c3e5f 16%, #3a537f 16%, #3a537f 83%, #2c3e5f 84%, #2c3e5f)
|
||||||
|
width: 50%
|
||||||
pointer-events: none
|
pointer-events: none
|
||||||
right: -16px
|
|
||||||
top: -7px
|
|
||||||
background: transparent url(/images/level/playback_thumb.png)
|
|
||||||
width: 32px
|
|
||||||
height: 32px
|
|
||||||
|
|
||||||
.btn-group
|
.scrubber-handle
|
||||||
// I don't know, I can figure this out for real later
|
position: absolute
|
||||||
margin: -26px 0 0 18px
|
pointer-events: none
|
||||||
|
right: -5px
|
||||||
|
top: -12.5px
|
||||||
|
background: linear-gradient(#2c3e5f, #2c3e5f 16%, #3a537f 16%, #3a537f 83%, #2c3e5f 84%, #2c3e5f)
|
||||||
|
width: 14px
|
||||||
|
height: 29px
|
||||||
|
border-radius: 3px
|
||||||
|
box-sizing: border-box
|
||||||
|
border: 1px solid black
|
||||||
|
box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.5)
|
||||||
|
|
||||||
.metrics
|
&:hover .steppers
|
||||||
display: inline-block
|
opacity: 1
|
||||||
margin: -30px 0 0 10px
|
|
||||||
vertical-align: middle
|
.steppers
|
||||||
|
position: absolute
|
||||||
|
z-index: 2
|
||||||
|
width: 10%
|
||||||
|
right: 2%
|
||||||
|
box-sizing: border-box
|
||||||
|
opacity: 0.25
|
||||||
|
|
||||||
|
button
|
||||||
|
height: 29px
|
||||||
|
|
||||||
|
.metrics
|
||||||
|
display: none
|
||||||
|
top: 30px
|
||||||
|
position: absolute
|
||||||
|
z-index: 10
|
||||||
|
pointer-events: none
|
||||||
|
padding: 10px
|
||||||
|
background: transparent url(/images/level/popover_background.png)
|
||||||
|
background-size: 100% 100%
|
||||||
|
font-variant: small-caps
|
||||||
|
text-overflow: ellipsis
|
||||||
|
font-size: 13px
|
||||||
|
white-space: nowrap
|
||||||
|
|
|
@ -8,7 +8,7 @@ block content
|
||||||
p(data-i18n="account_settings.not_logged_in") Log in or create an account to change your settings.
|
p(data-i18n="account_settings.not_logged_in") Log in or create an account to change your settings.
|
||||||
|
|
||||||
else
|
else
|
||||||
button.btn#save-button.disabled.hide(data-i18n="account_settings.autosave") Changes Save Automatically
|
button.btn#save-button.disabled.hide(data-i18n="account_settings.saveBackups") Changes Save Automatically
|
||||||
|
|
||||||
ul.nav.nav-tabs#settings-tabs
|
ul.nav.nav-tabs#settings-tabs
|
||||||
li
|
li
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
extends /templates/base
|
extends /templates/base
|
||||||
|
|
||||||
block content
|
block content
|
||||||
|
button(data-toggle="coco-modal", data-target="modal/revert", data-i18n="revert.revert").btn.btn-primary#revert-button Revert
|
||||||
button(data-i18n="article.edit_btn_preview").btn.btn-primary#preview-button Preview
|
button(data-i18n="article.edit_btn_preview").btn.btn-primary#preview-button Preview
|
||||||
button(data-toggle="coco-modal", data-target="modal/save_version", data-i18n="common.save").btn.btn-primary#save-button Save
|
button(data-toggle="coco-modal", data-target="modal/save_version", data-i18n="common.save").btn.btn-primary#save-button Save
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ block outer_content
|
||||||
span.level-title #{level.attributes.name}
|
span.level-title #{level.attributes.name}
|
||||||
|
|
||||||
.level-control-buttons
|
.level-control-buttons
|
||||||
|
button(data-toggle="coco-modal", data-target="modal/revert", data-i18n="revert.revert").btn.btn-primary#revert-button Revert
|
||||||
button(data-i18n="common.save").btn.btn-primary#commit-level-start-button Save
|
button(data-i18n="common.save").btn.btn-primary#commit-level-start-button Save
|
||||||
button(data-i18n="common.fork").btn.btn-primary#fork-level-start-button Fork
|
button(data-i18n="common.fork").btn.btn-primary#fork-level-start-button Fork
|
||||||
.btn-group.play-button-group
|
.btn-group.play-button-group
|
||||||
|
|
|
@ -6,6 +6,7 @@ block content
|
||||||
|
|
||||||
button.btn.btn-primary#save-button(data-toggle="coco-modal", data-target="modal/save_version")
|
button.btn.btn-primary#save-button(data-toggle="coco-modal", data-target="modal/save_version")
|
||||||
| Save
|
| Save
|
||||||
|
button.btn.btn-primary#revert-button(data-toggle="coco-modal", data-target="modal/revert", data-i18n="revert.revert") Revert
|
||||||
|
|
||||||
h3 Edit Thang Type: "#{thangType.attributes.name}"
|
h3 Edit Thang Type: "#{thangType.attributes.name}"
|
||||||
|
|
||||||
|
|
13
app/templates/modal/revert.jade
Normal file
13
app/templates/modal/revert.jade
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
extends /templates/modal/modal_base
|
||||||
|
|
||||||
|
block modal-header-content
|
||||||
|
h3(data-i18n="revert.revert_models") Revert Models
|
||||||
|
|
||||||
|
block modal-body-content
|
||||||
|
table.table.table-striped#changed-models
|
||||||
|
for model in models
|
||||||
|
tr
|
||||||
|
td
|
||||||
|
| #{model.type()}: #{model.get('name')}
|
||||||
|
td
|
||||||
|
button(value=model.id) Revert
|
|
@ -1,7 +1,7 @@
|
||||||
div.btn-group.cast-button-group
|
div.btn-group.cast-button-group
|
||||||
.button-progress-overlay
|
.button-progress-overlay
|
||||||
button.btn.btn-inverse.banner.cast-button(title=castShortcutVerbose + ": Cast current spell", data-i18n="play_level.tome_cast_button_cast") Spell Cast
|
button.btn.btn-inverse.btn-large.banner.cast-button(title=castShortcutVerbose + ": Cast current spell", data-i18n="play_level.tome_cast_button_cast") Spell Cast
|
||||||
button.btn.btn-inverse.banner.cast-options-button.dropdown-toggle(data-toggle="dropdown")
|
button.btn.btn-inverse.btn-large.banner.cast-options-button.dropdown-toggle(data-toggle="dropdown")
|
||||||
i.icon-cog.icon-white
|
i.icon-cog.icon-white
|
||||||
|
|
||||||
ul.dropdown-menu.autocast-delays
|
ul.dropdown-menu.autocast-delays
|
||||||
|
|
|
@ -1,22 +1,25 @@
|
||||||
.spell-progress
|
.flow
|
||||||
.progress
|
|
||||||
.bar
|
|
||||||
.scrubber-handle
|
|
||||||
|
|
||||||
.btn-group
|
.spell-progress
|
||||||
button.btn.btn-mini.btn-inverse.banner.step-backward(title="Ctrl/Cmd + Alt + [: Step Backward")
|
.progress
|
||||||
i.icon-arrow-left.icon-white
|
.bar
|
||||||
button.btn.btn-mini.btn-inverse.banner.step-forward(title="Ctrl/Cmd + Alt + ]: Step Forward")
|
.scrubber-handle
|
||||||
i.icon-arrow-right.icon-white
|
|
||||||
|
|
||||||
.metrics
|
.btn-group.steppers
|
||||||
code.statements-metric
|
button.btn.btn-mini.btn-inverse.banner.step-backward(title="Ctrl/Cmd + Alt + [: Step Backward")
|
||||||
span.metric.statement-index
|
i.icon-arrow-left.icon-white
|
||||||
| /
|
button.btn.btn-mini.btn-inverse.banner.step-forward(title="Ctrl/Cmd + Alt + ]: Step Forward")
|
||||||
span.metric.statements-executed
|
i.icon-arrow-right.icon-white
|
||||||
span.metric.statements-executed-total
|
|
||||||
|
|
.metrics
|
||||||
code.calls-metric
|
.statements-metric
|
||||||
span.metric.call-index
|
| Statement
|
||||||
| /
|
span.metric.statement-index
|
||||||
span.metric.calls-executed
|
| /
|
||||||
|
span.metric.statements-executed
|
||||||
|
span.metric.statements-executed-total
|
||||||
|
.calls-metric
|
||||||
|
| Call
|
||||||
|
span.metric.call-index
|
||||||
|
| /
|
||||||
|
span.metric.calls-executed
|
||||||
|
|
|
@ -16,6 +16,7 @@ module.exports = class ArticleEditView extends View
|
||||||
constructor: (options, @articleID) ->
|
constructor: (options, @articleID) ->
|
||||||
super options
|
super options
|
||||||
@article = new Article(_id: @articleID)
|
@article = new Article(_id: @articleID)
|
||||||
|
@article.saveBackups = true
|
||||||
@article.fetch()
|
@article.fetch()
|
||||||
@article.once('sync', @onArticleSync)
|
@article.once('sync', @onArticleSync)
|
||||||
@article.on('schema-loaded', @buildTreema)
|
@article.on('schema-loaded', @buildTreema)
|
||||||
|
@ -43,6 +44,8 @@ module.exports = class ArticleEditView extends View
|
||||||
@treema.build()
|
@treema.build()
|
||||||
|
|
||||||
pushChangesToPreview: =>
|
pushChangesToPreview: =>
|
||||||
|
for key, value of @treema.data
|
||||||
|
@article.set(key, value)
|
||||||
return unless @treema and @preview
|
return unless @treema and @preview
|
||||||
m = marked(@treema.data.body)
|
m = marked(@treema.data.body)
|
||||||
b = $(@preview.document.body)
|
b = $(@preview.document.body)
|
||||||
|
|
|
@ -31,8 +31,16 @@ module.exports = class ComponentsTabView extends View
|
||||||
haveThisComponent.push thang.id if haveThisComponent.length < 100 # for performance when adding many Thangs
|
haveThisComponent.push thang.id if haveThisComponent.length < 100 # for performance when adding many Thangs
|
||||||
return if _.isEqual presentComponents, @presentComponents
|
return if _.isEqual presentComponents, @presentComponents
|
||||||
@presentComponents = presentComponents
|
@presentComponents = presentComponents
|
||||||
treemaData = _.sortBy ({original: key.split('.')[0], majorVersion: parseInt(key.split('.')[1], 10), thangs: value, count: value.length} for key, value of @presentComponents), "count"
|
|
||||||
treemaData.reverse()
|
componentModels = @supermodel.getModels LevelComponent
|
||||||
|
componentModelMap = {}
|
||||||
|
componentModelMap[comp.get('original')] = comp for comp in componentModels
|
||||||
|
components = ({original: key.split('.')[0], majorVersion: parseInt(key.split('.')[1], 10), thangs: value, count: value.length} for key, value of @presentComponents)
|
||||||
|
treemaData = _.sortBy components, (comp) ->
|
||||||
|
comp = componentModelMap[comp.original]
|
||||||
|
res = [comp.get('system'), comp.get('name')]
|
||||||
|
return res
|
||||||
|
|
||||||
treemaOptions =
|
treemaOptions =
|
||||||
supermodel: @supermodel
|
supermodel: @supermodel
|
||||||
schema: {type: 'array', items: {type: 'object', format: 'level-component'}}
|
schema: {type: 'array', items: {type: 'object', format: 'level-component'}}
|
||||||
|
|
|
@ -36,6 +36,9 @@ module.exports = class EditorLevelView extends View
|
||||||
return false if @levelsLoaded > 1
|
return false if @levelsLoaded > 1
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
@supermodel.shouldSaveBackups = (model) ->
|
||||||
|
model.constructor.className in ['Level', 'LevelComponent', 'LevelSystem']
|
||||||
|
|
||||||
@level = new Level _id: @levelID
|
@level = new Level _id: @levelID
|
||||||
@level.once 'sync', @onLevelLoaded
|
@level.once 'sync', @onLevelLoaded
|
||||||
@supermodel.populateModel @level
|
@supermodel.populateModel @level
|
||||||
|
|
|
@ -30,6 +30,7 @@ module.exports = class SystemsTabView extends View
|
||||||
for system in @buildDefaultSystems()
|
for system in @buildDefaultSystems()
|
||||||
url = "/db/level.system/#{system.original}/version/#{system.majorVersion}"
|
url = "/db/level.system/#{system.original}/version/#{system.majorVersion}"
|
||||||
ls = new LevelSystem()
|
ls = new LevelSystem()
|
||||||
|
ls.saveBackups = true
|
||||||
do (url) -> ls.url = -> url
|
do (url) -> ls.url = -> url
|
||||||
continue if @supermodel.getModelByURL ls.url
|
continue if @supermodel.getModelByURL ls.url
|
||||||
ls.fetch()
|
ls.fetch()
|
||||||
|
@ -57,7 +58,12 @@ module.exports = class SystemsTabView extends View
|
||||||
unless systems.length
|
unless systems.length
|
||||||
systems = @buildDefaultSystems()
|
systems = @buildDefaultSystems()
|
||||||
insertedDefaults = true
|
insertedDefaults = true
|
||||||
systems = _.sortBy systems, "name"
|
|
||||||
|
systemModels = @supermodel.getModels LevelSystem
|
||||||
|
systemModelMap = {}
|
||||||
|
systemModelMap[sys.get('original')] = sys.get('name') for sys in systemModels
|
||||||
|
systems = _.sortBy systems, (sys) -> systemModelMap[sys.original]
|
||||||
|
|
||||||
treemaOptions =
|
treemaOptions =
|
||||||
# TODO: somehow get rid of the + button, or repurpose it to open the LevelSystemAddView instead
|
# TODO: somehow get rid of the + button, or repurpose it to open the LevelSystemAddView instead
|
||||||
supermodel: @supermodel
|
supermodel: @supermodel
|
||||||
|
|
|
@ -39,6 +39,7 @@ module.exports = class ThangTypeEditView extends View
|
||||||
super options
|
super options
|
||||||
@mockThang = _.cloneDeep(@mockThang)
|
@mockThang = _.cloneDeep(@mockThang)
|
||||||
@thangType = new ThangType(_id: @thangTypeID)
|
@thangType = new ThangType(_id: @thangTypeID)
|
||||||
|
@thangType.saveBackups = true
|
||||||
@thangType.fetch()
|
@thangType.fetch()
|
||||||
@thangType.once('sync', @onThangTypeSync)
|
@thangType.once('sync', @onThangTypeSync)
|
||||||
@refreshAnimation = _.debounce @refreshAnimation, 500
|
@refreshAnimation = _.debounce @refreshAnimation, 500
|
||||||
|
|
23
app/views/modal/revert_modal.coffee
Normal file
23
app/views/modal/revert_modal.coffee
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
ModalView = require 'views/kinds/ModalView'
|
||||||
|
template = require 'templates/modal/revert'
|
||||||
|
CocoModel = require 'models/CocoModel'
|
||||||
|
|
||||||
|
module.exports = class RevertModal extends ModalView
|
||||||
|
id: 'revert-modal'
|
||||||
|
template: template
|
||||||
|
|
||||||
|
events:
|
||||||
|
'click #changed-models button': 'onRevertModel'
|
||||||
|
|
||||||
|
onRevertModel: (e) ->
|
||||||
|
id = $(e.target).val()
|
||||||
|
CocoModel.backedUp[id].revert()
|
||||||
|
$(e.target).closest('tr').remove()
|
||||||
|
|
||||||
|
getRenderData: ->
|
||||||
|
c = super()
|
||||||
|
models = _.values CocoModel.backedUp
|
||||||
|
models = (m for m in models when m.hasLocalChanges())
|
||||||
|
c.models = models
|
||||||
|
c
|
||||||
|
|
|
@ -32,6 +32,9 @@ module.exports = class CastButtonView extends View
|
||||||
delay ?= 5000
|
delay ?= 5000
|
||||||
@setAutocastDelay delay
|
@setAutocastDelay delay
|
||||||
|
|
||||||
|
attachTo: (spellView) ->
|
||||||
|
@$el.detach().prependTo(spellView.toolbarView.$el).show()
|
||||||
|
|
||||||
hookUpButtons: ->
|
hookUpButtons: ->
|
||||||
# hook up cast button callbacks
|
# hook up cast button callbacks
|
||||||
@castButton = $('.cast-button', @$el)
|
@castButton = $('.cast-button', @$el)
|
||||||
|
|
|
@ -56,7 +56,6 @@ module.exports = class Spell
|
||||||
|
|
||||||
createAether: (thang) ->
|
createAether: (thang) ->
|
||||||
aetherOptions =
|
aetherOptions =
|
||||||
thisValue: thang.createUserContext()
|
|
||||||
problems:
|
problems:
|
||||||
jshint_W040: {level: "ignore"}
|
jshint_W040: {level: "ignore"}
|
||||||
aether_MissingThis: {level: (if thang.requiresThis then 'error' else 'warning')}
|
aether_MissingThis: {level: (if thang.requiresThis then 'error' else 'warning')}
|
||||||
|
@ -68,7 +67,7 @@ module.exports = class Spell
|
||||||
#callIndex: 0
|
#callIndex: 0
|
||||||
#timelessVariables: ['i']
|
#timelessVariables: ['i']
|
||||||
#statementIndex: 9001
|
#statementIndex: 9001
|
||||||
if not (me.team in @permissions.readwrite)# or @name is 'chooseAction' or thang.id is 'Thoktar' # Gridmancer can't handle it
|
if not (me.team in @permissions.readwrite) or window.currentView?.sessionID is "52bfb88099264e565d001349" # temp fix for debugger explosion bug
|
||||||
#console.log "Turning off includeFlow for", @spellKey
|
#console.log "Turning off includeFlow for", @spellKey
|
||||||
aetherOptions.includeFlow = false
|
aetherOptions.includeFlow = false
|
||||||
aether = new Aether aetherOptions
|
aether = new Aether aetherOptions
|
||||||
|
|
|
@ -1,51 +1,76 @@
|
||||||
View = require 'views/kinds/CocoView'
|
View = require 'views/kinds/CocoView'
|
||||||
template = require 'templates/play/level/tome/spell_debug'
|
template = require 'templates/play/level/tome/spell_debug'
|
||||||
Range = ace.require("ace/range").Range
|
Range = ace.require("ace/range").Range
|
||||||
|
TokenIterator = ace.require("ace/token_iterator").TokenIterator
|
||||||
|
serializedClasses =
|
||||||
|
Thang: require "lib/world/thang"
|
||||||
|
Vector: require "lib/world/vector"
|
||||||
|
Rectangle: require "lib/world/rectangle"
|
||||||
|
|
||||||
module.exports = class DebugView extends View
|
module.exports = class DebugView extends View
|
||||||
className: 'spell-debug-view'
|
className: 'spell-debug-view'
|
||||||
template: template
|
template: template
|
||||||
subscriptions: {}
|
|
||||||
|
subscriptions:
|
||||||
|
'god:new-world-created': 'onNewWorld'
|
||||||
|
|
||||||
events: {}
|
events: {}
|
||||||
|
|
||||||
constructor: (options) ->
|
constructor: (options) ->
|
||||||
super options
|
super options
|
||||||
@ace = options.ace
|
@ace = options.ace
|
||||||
|
@thang = options.thang
|
||||||
@variableStates = {}
|
@variableStates = {}
|
||||||
|
|
||||||
afterRender: ->
|
afterRender: ->
|
||||||
super()
|
super()
|
||||||
@ace.on "mousemove", @onMouseMove
|
@ace.on "mousemove", @onMouseMove
|
||||||
#@ace.on "click", onClick # same ACE API as mousemove
|
|
||||||
|
|
||||||
setVariableStates: (@variableStates) ->
|
setVariableStates: (@variableStates) ->
|
||||||
@update()
|
@update()
|
||||||
|
|
||||||
onMouseMove: (e) =>
|
onMouseMove: (e) =>
|
||||||
pos = e.getDocumentPosition()
|
pos = e.getDocumentPosition()
|
||||||
column = pos.column
|
endOfDoc = pos.row is @ace.getSession().getDocument().getLength() - 1
|
||||||
until column < 0
|
it = new TokenIterator e.editor.session, pos.row, pos.column
|
||||||
if token = e.editor.session.getTokenAt pos.row, column
|
isIdentifier = (t) -> t and (t.type is 'identifier' or t.value is 'this')
|
||||||
break if token.type is 'identifier'
|
while it.getCurrentTokenRow() is pos.row and not isIdentifier(token = it.getCurrentToken())
|
||||||
column = token.start - 1
|
it.stepBackward()
|
||||||
else
|
break unless token
|
||||||
--column
|
break if endOfDoc # Don't iterate backward on last line, since we might be way below.
|
||||||
if token?.type is 'identifier' and token.value of @variableStates
|
if isIdentifier token
|
||||||
@variable = token.value
|
# This could be a property access, like "enemy.target.pos" or "this.spawnedRectangles".
|
||||||
|
# We have to realize this and dig into the nesting of the objects.
|
||||||
|
start = it.getCurrentTokenColumn()
|
||||||
|
[chain, start, end] = [[token.value], start, start + token.value.length]
|
||||||
|
while it.getCurrentTokenRow() is pos.row
|
||||||
|
it.stepBackward()
|
||||||
|
break unless it.getCurrentToken()?.value is "."
|
||||||
|
it.stepBackward()
|
||||||
|
token = null # If we're doing a complex access like this.getEnemies().length, then length isn't a valid var.
|
||||||
|
break unless isIdentifier(prev = it.getCurrentToken())
|
||||||
|
token = prev
|
||||||
|
start = it.getCurrentTokenColumn()
|
||||||
|
chain.unshift token.value
|
||||||
|
if token and (token.value of @variableStates or token.value is "this")
|
||||||
|
@variableChain = chain
|
||||||
@pos = {left: e.domEvent.offsetX + 50, top: e.domEvent.offsetY + 50}
|
@pos = {left: e.domEvent.offsetX + 50, top: e.domEvent.offsetY + 50}
|
||||||
@markerRange = new Range pos.row, token.start, pos.row, token.start + token.value.length
|
@markerRange = new Range pos.row, start, pos.row, end
|
||||||
else
|
else
|
||||||
@variable = @markerRange = null
|
@variableChain = @markerRange = null
|
||||||
@update()
|
@update()
|
||||||
|
|
||||||
onMouseOut: (e) =>
|
onMouseOut: (e) =>
|
||||||
@variable = @markerRange = null
|
@variableChain = @markerRange = null
|
||||||
@update()
|
@update()
|
||||||
|
|
||||||
|
onNewWorld: (e) ->
|
||||||
|
@thang = @options.thang = e.world.thangMap[@thang.id] if @thang
|
||||||
|
|
||||||
update: ->
|
update: ->
|
||||||
if @variable
|
if @variableChain
|
||||||
value = @variableStates[@variable]
|
{key, value} = @deserializeVariableChain @variableChain
|
||||||
@$el.find("code").text "#{@variable}: #{value}"
|
@$el.find("code").text "#{key}: #{value}"
|
||||||
@$el.show().css(@pos)
|
@$el.show().css(@pos)
|
||||||
else
|
else
|
||||||
@$el.hide()
|
@$el.hide()
|
||||||
|
@ -58,6 +83,28 @@ module.exports = class DebugView extends View
|
||||||
if @markerRange
|
if @markerRange
|
||||||
@marker = @ace.getSession().addMarker @markerRange, "ace_bracket", "text"
|
@marker = @ace.getSession().addMarker @markerRange, "ace_bracket", "text"
|
||||||
|
|
||||||
|
deserializeVariableChain: (chain) ->
|
||||||
|
keys = []
|
||||||
|
for prop, i in chain
|
||||||
|
if prop is "this"
|
||||||
|
value = @thang
|
||||||
|
else
|
||||||
|
value = (if i is 0 then @variableStates else value)[prop]
|
||||||
|
keys.push prop
|
||||||
|
break unless value
|
||||||
|
if theClass = serializedClasses[value.CN]
|
||||||
|
if value.CN is "Thang"
|
||||||
|
thang = @thang.world.thangMap[value.id]
|
||||||
|
value = thang or "<Thang #{value.id} (non-existent)>"
|
||||||
|
else
|
||||||
|
value = theClass.deserializeFromAether(value)
|
||||||
|
if value and not _.isString value
|
||||||
|
if value.constructor?.className is "Thang"
|
||||||
|
value = "<#{value.spriteName} - #{value.id}, #{if value.pos then value.pos.toString() else 'non-physical'}>"
|
||||||
|
else
|
||||||
|
value = value.toString()
|
||||||
|
key: keys.join("."), value: value
|
||||||
|
|
||||||
destroy: ->
|
destroy: ->
|
||||||
super()
|
super()
|
||||||
@ace?.removeEventListener "mousemove", @onMouseMove
|
@ace?.removeEventListener "mousemove", @onMouseMove
|
||||||
|
|
|
@ -4,14 +4,15 @@ template = require 'templates/play/level/tome/spell_toolbar'
|
||||||
module.exports = class SpellToolbarView extends View
|
module.exports = class SpellToolbarView extends View
|
||||||
className: 'spell-toolbar-view'
|
className: 'spell-toolbar-view'
|
||||||
template: template
|
template: template
|
||||||
|
progressHoverDelay: 500
|
||||||
|
|
||||||
subscriptions:
|
subscriptions:
|
||||||
'spell-step-backward': 'onStepBackward'
|
'spell-step-backward': 'onStepBackward'
|
||||||
'spell-step-forward': 'onStepForward'
|
'spell-step-forward': 'onStepForward'
|
||||||
|
|
||||||
events:
|
events:
|
||||||
'mousemove .progress': 'onProgressHover'
|
'mousemove .spell-progress': 'onProgressHover'
|
||||||
'mouseout .progress': 'onProgressMouseOut'
|
'mouseout .spell-progress': 'onProgressMouseOut'
|
||||||
'click .step-backward': 'onStepBackward'
|
'click .step-backward': 'onStepBackward'
|
||||||
'click .step-forward': 'onStepForward'
|
'click .step-forward': 'onStepForward'
|
||||||
|
|
||||||
|
@ -22,20 +23,25 @@ module.exports = class SpellToolbarView extends View
|
||||||
afterRender: ->
|
afterRender: ->
|
||||||
super()
|
super()
|
||||||
|
|
||||||
|
toggleFlow: (to) ->
|
||||||
|
@$el.find(".flow").toggle to
|
||||||
|
|
||||||
setStatementIndex: (statementIndex) ->
|
setStatementIndex: (statementIndex) ->
|
||||||
return unless total = @callState?.statementsExecuted
|
return unless total = @callState?.statementsExecuted
|
||||||
@statementIndex = Math.min(total - 1, Math.max(0, statementIndex))
|
@statementIndex = Math.min(total - 1, Math.max(0, statementIndex))
|
||||||
@statementRatio = @statementIndex / (total - 1)
|
@statementRatio = @statementIndex / (total - 1)
|
||||||
@statementTime = @callState.statements[@statementIndex]?.userInfo.time ? 0
|
@statementTime = @callState.statements[@statementIndex]?.userInfo.time ? 0
|
||||||
@$el.find('.bar').css('width', 100 * @statementRatio + '%')
|
@$el.find('.bar').css('width', 100 * @statementRatio + '%')
|
||||||
Backbone.Mediator.publish 'tome:spell-statement-index-updated', statementIndex: @statementIndex, ace: @ace
|
|
||||||
@$el.find('.step-backward').prop('disabled', @statementIndex is 0)
|
@$el.find('.step-backward').prop('disabled', @statementIndex is 0)
|
||||||
@$el.find('.step-forward').prop('disabled', @statementIndex is total - 1)
|
@$el.find('.step-forward').prop('disabled', @statementIndex is total - 1)
|
||||||
@updateMetrics()
|
@updateMetrics()
|
||||||
|
_.defer =>
|
||||||
|
Backbone.Mediator.publish 'tome:spell-statement-index-updated', statementIndex: @statementIndex, ace: @ace
|
||||||
|
|
||||||
updateMetrics: ->
|
updateMetrics: ->
|
||||||
statementsExecuted = @callState.statementsExecuted
|
statementsExecuted = @callState.statementsExecuted
|
||||||
$metrics = @$el.find('.metrics')
|
$metrics = @$el.find('.metrics')
|
||||||
|
return $metrics.hide() if @suppressMetricsUpdates or not (statementsExecuted or @metrics.statementsExecuted)
|
||||||
if @metrics.callsExecuted > 1
|
if @metrics.callsExecuted > 1
|
||||||
$metrics.find('.call-index').text @callIndex + 1
|
$metrics.find('.call-index').text @callIndex + 1
|
||||||
$metrics.find('.calls-executed').text @metrics.callsExecuted
|
$metrics.find('.calls-executed').text @metrics.callsExecuted
|
||||||
|
@ -54,25 +60,40 @@ module.exports = class SpellToolbarView extends View
|
||||||
$metrics.find('.statements-metric').show().attr('title', "Statement #{@statementIndex + 1} of #{statementsExecuted} this call#{titleSuffix}")
|
$metrics.find('.statements-metric').show().attr('title', "Statement #{@statementIndex + 1} of #{statementsExecuted} this call#{titleSuffix}")
|
||||||
else
|
else
|
||||||
$metrics.find('.statements-metric').hide()
|
$metrics.find('.statements-metric').hide()
|
||||||
|
left = @$el.find('.scrubber-handle').position().left + @$el.find('.spell-progress').position().left
|
||||||
|
$metrics.finish().show().css({left: left - $metrics.width() / 2}).delay(2000).fadeOut('fast')
|
||||||
|
|
||||||
setStatementRatio: (ratio) ->
|
setStatementRatio: (ratio) ->
|
||||||
return unless total = @callState?.statementsExecuted
|
return unless total = @callState?.statementsExecuted
|
||||||
@setStatementIndex Math.floor ratio * total
|
statementIndex = Math.floor ratio * total
|
||||||
|
@setStatementIndex statementIndex unless statementIndex is @statementIndex
|
||||||
|
|
||||||
onProgressHover: (e) ->
|
onProgressHover: (e) ->
|
||||||
|
return @onProgressHoverLong(e) if @maintainIndexHover
|
||||||
|
@lastHoverEvent = e
|
||||||
|
@hoverTimeout = _.delay @onProgressHoverLong, @progressHoverDelay unless @hoverTimeout
|
||||||
|
|
||||||
|
onProgressHoverLong: (e) =>
|
||||||
|
e ?= @lastHoverEvent
|
||||||
|
@hoverTimeout = null
|
||||||
@setStatementRatio e.offsetX / @$el.find('.progress').width()
|
@setStatementRatio e.offsetX / @$el.find('.progress').width()
|
||||||
@updateTime()
|
@updateTime()
|
||||||
@maintainIndexHover = true
|
@maintainIndexHover = true
|
||||||
|
@updateScroll()
|
||||||
|
|
||||||
onProgressMouseOut: (e) ->
|
onProgressMouseOut: (e) ->
|
||||||
@maintainIndexHover = false
|
@maintainIndexHover = false
|
||||||
|
if @hoverTimeout
|
||||||
|
clearTimeout @hoverTimeout
|
||||||
|
@hoverTimeout = null
|
||||||
|
|
||||||
onStepBackward: (e) -> @step -1
|
onStepBackward: (e) -> @step -1
|
||||||
onStepForward: (e) -> @step 1
|
onStepForward: (e) -> @step 1
|
||||||
step: (delta) ->
|
step: (delta) ->
|
||||||
lastTime = @statementTime
|
lastTime = @statementTime
|
||||||
@setStatementIndex @statementIndex + delta
|
@setStatementIndex @statementIndex + delta
|
||||||
@updateTime() if @statementIndex isnt lastTime
|
@updateTime() if @statementTime isnt lastTime
|
||||||
|
@updateScroll()
|
||||||
|
|
||||||
updateTime: ->
|
updateTime: ->
|
||||||
@maintainIndexScrub = true
|
@maintainIndexScrub = true
|
||||||
|
@ -80,15 +101,18 @@ module.exports = class SpellToolbarView extends View
|
||||||
@maintainIndexScrubTimeout = _.delay (=> @maintainIndexScrub = false), 500
|
@maintainIndexScrubTimeout = _.delay (=> @maintainIndexScrub = false), 500
|
||||||
Backbone.Mediator.publish 'level-set-time', time: @statementTime, scrubDuration: 500
|
Backbone.Mediator.publish 'level-set-time', time: @statementTime, scrubDuration: 500
|
||||||
|
|
||||||
|
updateScroll: ->
|
||||||
|
return unless statementStart = @callState?.statements?[@statementIndex]?.range[0]
|
||||||
|
text = @ace.getValue()
|
||||||
|
currentLine = text.substr(0, statementStart).split('\n').length - 1
|
||||||
|
@ace.scrollToLine currentLine, true, true
|
||||||
|
|
||||||
setCallState: (callState, statementIndex, @callIndex, @metrics) ->
|
setCallState: (callState, statementIndex, @callIndex, @metrics) ->
|
||||||
return if callState is @callState and statementIndex is @statementIndex
|
return if callState is @callState and statementIndex is @statementIndex
|
||||||
return unless @callState = callState
|
return unless @callState = callState
|
||||||
|
@suppressMetricsUpdates = true
|
||||||
if not @maintainIndexHover and not @maintainIndexScrub and statementIndex? and callState.statements[statementIndex]?.userInfo.time isnt @statementTime
|
if not @maintainIndexHover and not @maintainIndexScrub and statementIndex? and callState.statements[statementIndex]?.userInfo.time isnt @statementTime
|
||||||
@setStatementIndex statementIndex
|
@setStatementIndex statementIndex
|
||||||
else
|
else
|
||||||
@setStatementRatio @statementRatio
|
@setStatementRatio @statementRatio
|
||||||
# Not sure yet whether it's better to maintain @statementIndex or @statementRatio
|
@suppressMetricsUpdates = false
|
||||||
#else if @statementRatio is 1 or not @statementIndex?
|
|
||||||
# @setStatementRatio 1
|
|
||||||
#else
|
|
||||||
# @setStatementIndex @statementIndex
|
|
||||||
|
|
|
@ -51,7 +51,6 @@ module.exports = class SpellView extends View
|
||||||
else
|
else
|
||||||
# needs to happen after the code generating this view is complete
|
# needs to happen after the code generating this view is complete
|
||||||
setTimeout @onLoaded, 1
|
setTimeout @onLoaded, 1
|
||||||
@createDebugView()
|
|
||||||
|
|
||||||
createACE: ->
|
createACE: ->
|
||||||
# Test themes and settings here: http://ace.ajax.org/build/kitchen-sink.html
|
# Test themes and settings here: http://ace.ajax.org/build/kitchen-sink.html
|
||||||
|
@ -69,6 +68,7 @@ module.exports = class SpellView extends View
|
||||||
@ace.setShowPrintMargin false
|
@ace.setShowPrintMargin false
|
||||||
@ace.setShowInvisibles false
|
@ace.setShowInvisibles false
|
||||||
@ace.setBehavioursEnabled false
|
@ace.setBehavioursEnabled false
|
||||||
|
@ace.setAnimatedScroll true
|
||||||
@toggleControls null, @writable
|
@toggleControls null, @writable
|
||||||
@aceSession.selection.on 'changeCursor', @onCursorActivity
|
@aceSession.selection.on 'changeCursor', @onCursorActivity
|
||||||
$(@ace.container).find('.ace_gutter').on 'click', '.ace_error, .ace_warning, .ace_info', @onAnnotationClick
|
$(@ace.container).find('.ace_gutter').on 'click', '.ace_error, .ace_warning, .ace_info', @onAnnotationClick
|
||||||
|
@ -154,14 +154,15 @@ module.exports = class SpellView extends View
|
||||||
@spell.loaded = true
|
@spell.loaded = true
|
||||||
Backbone.Mediator.publish 'tome:spell-loaded', spell: @spell
|
Backbone.Mediator.publish 'tome:spell-loaded', spell: @spell
|
||||||
@eventsSuppressed = false # Now that the initial change is in, we can start running any changed code
|
@eventsSuppressed = false # Now that the initial change is in, we can start running any changed code
|
||||||
|
@createToolbarView()
|
||||||
|
|
||||||
createDebugView: ->
|
createDebugView: ->
|
||||||
@debugView = new SpellDebugView ace: @ace
|
@debugView = new SpellDebugView ace: @ace, thang: @thang
|
||||||
@$el.append @debugView.render().$el.hide()
|
@$el.append @debugView.render().$el.hide()
|
||||||
|
|
||||||
createToolbarView: ->
|
createToolbarView: ->
|
||||||
@toolbarView = new SpellToolbarView ace: @ace
|
@toolbarView = new SpellToolbarView ace: @ace
|
||||||
@$el.prepend @toolbarView.render().$el
|
@$el.append @toolbarView.render().$el
|
||||||
|
|
||||||
onMouseOut: (e) ->
|
onMouseOut: (e) ->
|
||||||
@debugView.onMouseOut e
|
@debugView.onMouseOut e
|
||||||
|
@ -174,6 +175,8 @@ module.exports = class SpellView extends View
|
||||||
return if thang.id is @thang?.id
|
return if thang.id is @thang?.id
|
||||||
@thang = thang
|
@thang = thang
|
||||||
@spellThang = @spell.thangs[@thang.id]
|
@spellThang = @spell.thangs[@thang.id]
|
||||||
|
@createDebugView() unless @debugView
|
||||||
|
@debugView.thang = @thang
|
||||||
@updateAether false, true
|
@updateAether false, true
|
||||||
@highlightCurrentLine()
|
@highlightCurrentLine()
|
||||||
|
|
||||||
|
@ -343,6 +346,7 @@ module.exports = class SpellView extends View
|
||||||
@spellHasChanged = true
|
@spellHasChanged = true
|
||||||
|
|
||||||
onSessionWillSave: (e) ->
|
onSessionWillSave: (e) ->
|
||||||
|
return unless @spellHasChanged
|
||||||
setTimeout(=>
|
setTimeout(=>
|
||||||
unless @spellHasChanged
|
unless @spellHasChanged
|
||||||
@$el.find('.save-status').finish().show().fadeOut(2000)
|
@$el.find('.save-status').finish().show().fadeOut(2000)
|
||||||
|
@ -432,35 +436,32 @@ module.exports = class SpellView extends View
|
||||||
@debugView.setVariableStates {}
|
@debugView.setVariableStates {}
|
||||||
@aceSession.removeGutterDecoration row, 'executing' for row in [0 ... @aceSession.getLength()]
|
@aceSession.removeGutterDecoration row, 'executing' for row in [0 ... @aceSession.getLength()]
|
||||||
$(@ace.container).find('.ace_gutter-cell.executing').removeClass('executing')
|
$(@ace.container).find('.ace_gutter-cell.executing').removeClass('executing')
|
||||||
unless executed.length
|
if not executed.length or (@spell.name is "plan" and @spellThang.castAether.metrics.statementsExecuted < 20)
|
||||||
@toolbarView?.$el.hide()
|
@toolbarView?.toggleFlow false
|
||||||
return
|
return
|
||||||
unless @toolbarView or (@spell.name is "plan" and @spellThang.castAether.metrics.statementsExecuted < 20)
|
|
||||||
@createToolbarView()
|
|
||||||
lastExecuted = _.last executed
|
lastExecuted = _.last executed
|
||||||
@toolbarView?.$el.show()
|
@toolbarView?.toggleFlow true
|
||||||
statementIndex = Math.max 0, lastExecuted.length - 1
|
statementIndex = Math.max 0, lastExecuted.length - 1
|
||||||
@toolbarView?.setCallState states[currentCallIndex], statementIndex, currentCallIndex, @spellThang.castAether.metrics
|
@toolbarView?.setCallState states[currentCallIndex], statementIndex, currentCallIndex, @spellThang.castAether.metrics
|
||||||
marked = {}
|
marked = {}
|
||||||
lastExecuted = lastExecuted[0 .. @toolbarView.statementIndex] if @toolbarView?.statementIndex?
|
lastExecuted = lastExecuted[0 .. @toolbarView.statementIndex] if @toolbarView?.statementIndex?
|
||||||
for state, i in lastExecuted
|
for state, i in lastExecuted
|
||||||
#clazz = if state.executing then 'executing' else 'executed' # doesn't work
|
[start, end] = [offsetToPos(state.range[0]), offsetToPos(state.range[1])]
|
||||||
clazz = if i is lastExecuted.length - 1 then 'executing' else 'executed'
|
clazz = if i is lastExecuted.length - 1 then 'executing' else 'executed'
|
||||||
if clazz is 'executed'
|
if clazz is 'executed'
|
||||||
key = state.range[0] + '_' + state.range[1]
|
continue if marked[start.row]
|
||||||
continue if marked[key] > 2 # don't allow more than three of the same marker
|
marked[start.row] = true
|
||||||
marked[key] ?= 0
|
markerType = "fullLine"
|
||||||
++marked[key]
|
|
||||||
else
|
else
|
||||||
@debugView.setVariableStates state.variables
|
@debugView.setVariableStates state.variables
|
||||||
#console.log "at", state.userInfo.time, "vars are now:", state.variables
|
markerType = "text"
|
||||||
[start, end] = [offsetToPos(state.range[0]), offsetToPos(state.range[1])]
|
|
||||||
markerRange = new Range(start.row, start.column, end.row, end.column)
|
markerRange = new Range(start.row, start.column, end.row, end.column)
|
||||||
markerRange.start = @aceDoc.createAnchor markerRange.start
|
markerRange.start = @aceDoc.createAnchor markerRange.start
|
||||||
markerRange.end = @aceDoc.createAnchor markerRange.end
|
markerRange.end = @aceDoc.createAnchor markerRange.end
|
||||||
markerRange.id = @aceSession.addMarker markerRange, clazz, "text"
|
markerRange.id = @aceSession.addMarker markerRange, clazz, markerType
|
||||||
@markerRanges.push markerRange
|
@markerRanges.push markerRange
|
||||||
@aceSession.addGutterDecoration start.row, clazz if clazz is 'executing'
|
@aceSession.addGutterDecoration start.row, clazz if clazz is 'executing'
|
||||||
|
null
|
||||||
|
|
||||||
onAnnotationClick: ->
|
onAnnotationClick: ->
|
||||||
alertBox = $("<div class='alert alert-info fade in'>#{msg}</div>")
|
alertBox = $("<div class='alert alert-info fade in'>#{msg}</div>")
|
||||||
|
|
|
@ -138,7 +138,7 @@ module.exports = class TomeView extends View
|
||||||
@$el.find('#' + @spellTabView.id).after(@spellTabView.el).remove()
|
@$el.find('#' + @spellTabView.id).after(@spellTabView.el).remove()
|
||||||
@spellView.setThang thang
|
@spellView.setThang thang
|
||||||
@spellTabView.setThang thang
|
@spellTabView.setThang thang
|
||||||
@castButton.$el.show()
|
@castButton.attachTo @spellView
|
||||||
@thangList.$el.hide()
|
@thangList.$el.hide()
|
||||||
@spellPaletteView = @insertSubView new SpellPaletteView thang: thang
|
@spellPaletteView = @insertSubView new SpellPaletteView thang: thang
|
||||||
@spellPaletteView.toggleControls {}, @spellView.controlsEnabled # TODO: know when palette should have been disabled but didn't exist
|
@spellPaletteView.toggleControls {}, @spellView.controlsEnabled # TODO: know when palette should have been disabled but didn't exist
|
||||||
|
|
|
@ -70,8 +70,8 @@ module.exports = class PlayLevelView extends View
|
||||||
$('body').append($("<img src='http://code.org/api/hour/begin_codecombat.png' style='visibility: hidden;'>"))
|
$('body').append($("<img src='http://code.org/api/hour/begin_codecombat.png' style='visibility: hidden;'>"))
|
||||||
window.tracker?.trackEvent 'Hour of Code Begin', {}
|
window.tracker?.trackEvent 'Hour of Code Begin', {}
|
||||||
|
|
||||||
@isEditorPreview = @getQueryVariable "dev"
|
@isEditorPreview = @getQueryVariable 'dev'
|
||||||
@sessionID = @getQueryVariable "session"
|
@sessionID = @getQueryVariable 'session'
|
||||||
|
|
||||||
$(window).on('resize', @onWindowResize)
|
$(window).on('resize', @onWindowResize)
|
||||||
@supermodel.once 'error', =>
|
@supermodel.once 'error', =>
|
||||||
|
@ -79,7 +79,14 @@ module.exports = class PlayLevelView extends View
|
||||||
@$el.html('<div class="alert">' + msg + '</div>')
|
@$el.html('<div class="alert">' + msg + '</div>')
|
||||||
@saveScreenshot = _.throttle @saveScreenshot, 30000
|
@saveScreenshot = _.throttle @saveScreenshot, 30000
|
||||||
|
|
||||||
@load() unless @isEditorPreview
|
if @isEditorPreview
|
||||||
|
f = =>
|
||||||
|
@supermodel.shouldSaveBackups = (model) ->
|
||||||
|
model.constructor.className in ['Level', 'LevelComponent', 'LevelSystem']
|
||||||
|
@load() unless @levelLoader
|
||||||
|
setTimeout f, 100
|
||||||
|
else
|
||||||
|
@load()
|
||||||
|
|
||||||
# Save latest level played in local storage
|
# Save latest level played in local storage
|
||||||
if localStorage?
|
if localStorage?
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
"firepad": "~0.1.2",
|
"firepad": "~0.1.2",
|
||||||
"marked": "~0.3.0",
|
"marked": "~0.3.0",
|
||||||
"moment": "~2.5.0",
|
"moment": "~2.5.0",
|
||||||
"aether": "~0.0.5",
|
"aether": "~0.0.7",
|
||||||
"underscore.string": "~2.3.3",
|
"underscore.string": "~2.3.3",
|
||||||
"firebase": "~1.0.2"
|
"firebase": "~1.0.2"
|
||||||
},
|
},
|
||||||
|
|
|
@ -18,6 +18,7 @@ logging = require './server/commons/logging'
|
||||||
sprites = require './server/routes/sprites'
|
sprites = require './server/routes/sprites'
|
||||||
contact = require './server/routes/contact'
|
contact = require './server/routes/contact'
|
||||||
languages = require './server/routes/languages'
|
languages = require './server/routes/languages'
|
||||||
|
mail = require './server/routes/mail'
|
||||||
|
|
||||||
https = require 'https'
|
https = require 'https'
|
||||||
http = require 'http'
|
http = require 'http'
|
||||||
|
@ -82,6 +83,7 @@ contact.setupRoutes(app)
|
||||||
file.setupRoutes(app)
|
file.setupRoutes(app)
|
||||||
folder.setupRoutes(app)
|
folder.setupRoutes(app)
|
||||||
languages.setupRoutes(app)
|
languages.setupRoutes(app)
|
||||||
|
mail.setupRoutes(app)
|
||||||
|
|
||||||
# Some sort of cross-domain communication hack facebook requires
|
# Some sort of cross-domain communication hack facebook requires
|
||||||
app.get('/channel.html', (req, res) ->
|
app.get('/channel.html', (req, res) ->
|
||||||
|
|
19
server/commons/mail.coffee
Normal file
19
server/commons/mail.coffee
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
config = require '../../server_config'
|
||||||
|
|
||||||
|
module.exports.MAILCHIMP_LIST_ID = 'e9851239eb'
|
||||||
|
module.exports.MAILCHIMP_GROUP_ID = '4529'
|
||||||
|
module.exports.MAILCHIMP_GROUP_MAP =
|
||||||
|
announcement: 'Announcements'
|
||||||
|
tester: 'Adventurers'
|
||||||
|
level_creator: 'Artisans'
|
||||||
|
developer: 'Archmages'
|
||||||
|
article_editor: 'Scribes'
|
||||||
|
translator: 'Diplomats'
|
||||||
|
support: 'Ambassadors'
|
||||||
|
|
||||||
|
nodemailer = require 'nodemailer'
|
||||||
|
module.exports.transport = nodemailer.createTransport "SMTP",
|
||||||
|
service: config.mail.service
|
||||||
|
user: config.mail.username
|
||||||
|
pass: config.mail.password
|
||||||
|
authMethod: "LOGIN"
|
|
@ -4,8 +4,8 @@ LocalStrategy = require('passport-local').Strategy
|
||||||
User = require('../users/User')
|
User = require('../users/User')
|
||||||
UserHandler = require('../users/user_handler')
|
UserHandler = require('../users/user_handler')
|
||||||
config = require '../../server_config'
|
config = require '../../server_config'
|
||||||
nodemailer = require 'nodemailer'
|
|
||||||
errors = require '../commons/errors'
|
errors = require '../commons/errors'
|
||||||
|
mail = require '../commons/mail'
|
||||||
|
|
||||||
module.exports.setupRoutes = (app) ->
|
module.exports.setupRoutes = (app) ->
|
||||||
passport.serializeUser((user, done) -> done(null, user._id))
|
passport.serializeUser((user, done) -> done(null, user._id))
|
||||||
|
@ -66,9 +66,8 @@ module.exports.setupRoutes = (app) ->
|
||||||
user.save (err) =>
|
user.save (err) =>
|
||||||
return errors.serverError(res) if err
|
return errors.serverError(res) if err
|
||||||
if config.isProduction
|
if config.isProduction
|
||||||
transport = createSMTPTransport()
|
|
||||||
options = createMailOptions req.body.email, user.get('passwordReset')
|
options = createMailOptions req.body.email, user.get('passwordReset')
|
||||||
transport.sendMail options, (error, response) ->
|
mail.transport.sendMail options, (error, response) ->
|
||||||
if error
|
if error
|
||||||
console.error "Error sending mail: #{error.message or error}"
|
console.error "Error sending mail: #{error.message or error}"
|
||||||
return errors.serverError(res) if err
|
return errors.serverError(res) if err
|
||||||
|
@ -104,13 +103,4 @@ createMailOptions = (receiver, password) ->
|
||||||
replyTo: config.mail.username
|
replyTo: config.mail.username
|
||||||
subject: "[CodeCombat] Password Reset"
|
subject: "[CodeCombat] Password Reset"
|
||||||
text: "You can log into your account with: #{password}"
|
text: "You can log into your account with: #{password}"
|
||||||
#html: message.replace '\n', '<br>\n'
|
#
|
||||||
|
|
||||||
createSMTPTransport = ->
|
|
||||||
return smtpTransport if smtpTransport
|
|
||||||
smtpTransport = nodemailer.createTransport "SMTP",
|
|
||||||
service: config.mail.service
|
|
||||||
user: config.mail.username
|
|
||||||
pass: config.mail.password
|
|
||||||
authMethod: "LOGIN"
|
|
||||||
smtpTransport
|
|
|
@ -1,14 +1,13 @@
|
||||||
config = require '../../server_config'
|
config = require '../../server_config'
|
||||||
winston = require 'winston'
|
winston = require 'winston'
|
||||||
nodemailer = require 'nodemailer'
|
mail = require '../commons/mail'
|
||||||
|
|
||||||
module.exports.setupRoutes = (app) ->
|
module.exports.setupRoutes = (app) ->
|
||||||
app.post '/contact', (req, res) ->
|
app.post '/contact', (req, res) ->
|
||||||
winston.info "Sending mail from #{req.body.email} saying #{req.body.message}"
|
winston.info "Sending mail from #{req.body.email} saying #{req.body.message}"
|
||||||
if config.isProduction
|
if config.isProduction
|
||||||
transport = createSMTPTransport()
|
|
||||||
options = createMailOptions req.body.email, req.body.message, req.user
|
options = createMailOptions req.body.email, req.body.message, req.user
|
||||||
transport.sendMail options, (error, response) ->
|
mail.transport.sendMail options, (error, response) ->
|
||||||
if error
|
if error
|
||||||
winston.error "Error sending mail: #{error.message or error}"
|
winston.error "Error sending mail: #{error.message or error}"
|
||||||
else
|
else
|
||||||
|
@ -17,7 +16,6 @@ module.exports.setupRoutes = (app) ->
|
||||||
|
|
||||||
createMailOptions = (sender, message, user) ->
|
createMailOptions = (sender, message, user) ->
|
||||||
# TODO: use email templates here
|
# TODO: use email templates here
|
||||||
console.log 'text is now', "#{message}\n\n#{user.get('name')}\nID: #{user._id}"
|
|
||||||
options =
|
options =
|
||||||
from: config.mail.username
|
from: config.mail.username
|
||||||
to: config.mail.username
|
to: config.mail.username
|
||||||
|
@ -25,13 +23,3 @@ createMailOptions = (sender, message, user) ->
|
||||||
subject: "[CodeCombat] Feedback - #{sender}"
|
subject: "[CodeCombat] Feedback - #{sender}"
|
||||||
text: "#{message}\n\nUsername: #{user.get('name') or 'Anonymous'}\nID: #{user._id}"
|
text: "#{message}\n\nUsername: #{user.get('name') or 'Anonymous'}\nID: #{user._id}"
|
||||||
#html: message.replace '\n', '<br>\n'
|
#html: message.replace '\n', '<br>\n'
|
||||||
|
|
||||||
smtpTransport = null
|
|
||||||
createSMTPTransport = ->
|
|
||||||
return smtpTransport if smtpTransport
|
|
||||||
smtpTransport = nodemailer.createTransport "SMTP",
|
|
||||||
service: config.mail.service
|
|
||||||
user: config.mail.username
|
|
||||||
pass: config.mail.password
|
|
||||||
authMethod: "LOGIN"
|
|
||||||
smtpTransport
|
|
61
server/routes/mail.coffee
Normal file
61
server/routes/mail.coffee
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
mail = require '../commons/mail'
|
||||||
|
map = _.invert mail.MAILCHIMP_GROUP_MAP
|
||||||
|
User = require '../users/User.coffee'
|
||||||
|
errors = require '../commons/errors'
|
||||||
|
#request = require 'request'
|
||||||
|
config = require '../../server_config'
|
||||||
|
|
||||||
|
#badLog = (text) ->
|
||||||
|
# console.log text
|
||||||
|
# request.post 'http://requestb.in/1brdpaz1', { form: {log: text} }
|
||||||
|
|
||||||
|
module.exports.setupRoutes = (app) ->
|
||||||
|
app.all config.mail.mailchimpWebhook, (req, res) ->
|
||||||
|
post = req.body
|
||||||
|
# badLog("Got post data: #{JSON.stringify(post, null, '\t')}")
|
||||||
|
|
||||||
|
unless post.type in ['unsubscribe', 'profile']
|
||||||
|
res.send 'Bad post type'
|
||||||
|
return res.end()
|
||||||
|
|
||||||
|
unless post.data.email
|
||||||
|
res.send 'No email provided'
|
||||||
|
return res.end()
|
||||||
|
|
||||||
|
query = {'mailChimp.leid':post.data.web_id}
|
||||||
|
User.findOne query, (err, user) ->
|
||||||
|
return errors.serverError(res) if err
|
||||||
|
if not user
|
||||||
|
return errors.notFound(res)
|
||||||
|
|
||||||
|
handleProfileUpdate(user, post) if post.type is 'profile'
|
||||||
|
handleUnsubscribe(user) if post.type is 'unsubscribe'
|
||||||
|
|
||||||
|
user.updatedMailChimp = true # so as not to echo back to mailchimp
|
||||||
|
user.save (err) ->
|
||||||
|
return errors.serverError(res) if err
|
||||||
|
res.end('Success')
|
||||||
|
|
||||||
|
|
||||||
|
handleProfileUpdate = (user, post) ->
|
||||||
|
groups = post.data.merges.INTERESTS.split(', ')
|
||||||
|
groups = (map[g] for g in groups when map[g])
|
||||||
|
otherSubscriptions = (g for g in user.get('emailSubscriptions') when not mail.MAILCHIMP_GROUP_MAP[g])
|
||||||
|
groups = groups.concat otherSubscriptions
|
||||||
|
user.set 'emailSubscriptions', groups
|
||||||
|
|
||||||
|
fname = post.data.merges.FNAME
|
||||||
|
user.set('firstName', fname) if fname
|
||||||
|
|
||||||
|
lname = post.data.merges.LNAME
|
||||||
|
user.set('lastName', lname) if lname
|
||||||
|
|
||||||
|
user.set 'mailChimp.email', post.data.email
|
||||||
|
user.set 'mailChimp.euid', post.data.id
|
||||||
|
|
||||||
|
# badLog("Updating user object to: #{JSON.stringify(user.toObject(), null, '\t')}")
|
||||||
|
|
||||||
|
handleUnsubscribe = (user) ->
|
||||||
|
user.set 'emailSubscriptions', []
|
||||||
|
|
||||||
|
# badLog("Unsubscribing user object to: #{JSON.stringify(user.toObject(), null, '\t')}")
|
|
@ -2,6 +2,7 @@ mongoose = require('mongoose')
|
||||||
jsonschema = require('./user_schema')
|
jsonschema = require('./user_schema')
|
||||||
crypto = require('crypto')
|
crypto = require('crypto')
|
||||||
{salt, isProduction} = require('../../server_config')
|
{salt, isProduction} = require('../../server_config')
|
||||||
|
mail = require '../commons/mail'
|
||||||
|
|
||||||
sendwithus = require '../sendwithus'
|
sendwithus = require '../sendwithus'
|
||||||
|
|
||||||
|
@ -34,16 +35,17 @@ UserSchema.statics.updateMailChimp = (doc, callback) ->
|
||||||
existingProps = doc.get('mailChimp')
|
existingProps = doc.get('mailChimp')
|
||||||
emailChanged = (not existingProps) or existingProps?.email isnt doc.get('email')
|
emailChanged = (not existingProps) or existingProps?.email isnt doc.get('email')
|
||||||
emailSubs = doc.get('emailSubscriptions')
|
emailSubs = doc.get('emailSubscriptions')
|
||||||
newGroups = (groupingMap[name] for name in emailSubs when groupingMap[name]?)
|
gm = mail.MAILCHIMP_GROUP_MAP
|
||||||
|
newGroups = (gm[name] for name in emailSubs when gm[name]?)
|
||||||
if (not existingProps) and newGroups.length is 0
|
if (not existingProps) and newGroups.length is 0
|
||||||
return callback?() # don't add totally unsubscribed people to the list
|
return callback?() # don't add totally unsubscribed people to the list
|
||||||
subsChanged = doc.currentSubscriptions isnt JSON.stringify(emailSubs)
|
subsChanged = doc.currentSubscriptions isnt JSON.stringify(emailSubs)
|
||||||
return callback?() unless emailChanged or subsChanged
|
return callback?() unless emailChanged or subsChanged
|
||||||
|
|
||||||
params = {}
|
params = {}
|
||||||
params.id = MAILCHIMP_LIST_ID
|
params.id = mail.MAILCHIMP_LIST_ID
|
||||||
params.email = if existingProps then {leid:existingProps.leid} else {email:doc.get('email')}
|
params.email = if existingProps then {leid:existingProps.leid} else {email:doc.get('email')}
|
||||||
params.merge_vars = { groupings: [ {id: MAILCHIMP_GROUP_ID, groups: newGroups} ] }
|
params.merge_vars = { groupings: [ {id: mail.MAILCHIMP_GROUP_ID, groups: newGroups} ] }
|
||||||
params.update_existing = true
|
params.update_existing = true
|
||||||
params.double_optin = false
|
params.double_optin = false
|
||||||
|
|
||||||
|
@ -79,18 +81,6 @@ UserSchema.pre('save', (next) ->
|
||||||
next()
|
next()
|
||||||
)
|
)
|
||||||
|
|
||||||
MAILCHIMP_LIST_ID = 'e9851239eb'
|
|
||||||
MAILCHIMP_GROUP_ID = '4529'
|
|
||||||
|
|
||||||
groupingMap =
|
|
||||||
announcement: 'Announcements'
|
|
||||||
tester: 'Adventurers'
|
|
||||||
level_creator: 'Artisans'
|
|
||||||
developer: 'Archmages'
|
|
||||||
article_editor: 'Scribes'
|
|
||||||
translator: 'Diplomats'
|
|
||||||
support: 'Ambassadors'
|
|
||||||
|
|
||||||
UserSchema.post 'save', (doc) ->
|
UserSchema.post 'save', (doc) ->
|
||||||
UserSchema.statics.updateMailChimp(doc)
|
UserSchema.statics.updateMailChimp(doc)
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ config.mail.service = process.env.COCO_MAIL_SERVICE_NAME || "Zoho";
|
||||||
config.mail.username = process.env.COCO_MAIL_SERVICE_USERNAME || "";
|
config.mail.username = process.env.COCO_MAIL_SERVICE_USERNAME || "";
|
||||||
config.mail.password = process.env.COCO_MAIL_SERVICE_PASSWORD || "";
|
config.mail.password = process.env.COCO_MAIL_SERVICE_PASSWORD || "";
|
||||||
config.mail.mailchimpAPIKey = process.env.COCO_MAILCHIMP_API_KEY || '';
|
config.mail.mailchimpAPIKey = process.env.COCO_MAILCHIMP_API_KEY || '';
|
||||||
|
config.mail.mailchimpWebhook = process.env.COCO_MAILCHIMP_WEBHOOK || '/mail/webhook';
|
||||||
config.mail.sendwithusAPIKey = process.env.COCO_SENDWITHUS_API_KEY || '';
|
config.mail.sendwithusAPIKey = process.env.COCO_SENDWITHUS_API_KEY || '';
|
||||||
|
|
||||||
config.salt = process.env.COCO_SALT || 'pepper';
|
config.salt = process.env.COCO_SALT || 'pepper';
|
||||||
|
|
Loading…
Reference in a new issue