mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-12-12 08:41:46 -05:00
Merge branch 'master' into production
This commit is contained in:
commit
9ad181bc10
12 changed files with 368 additions and 54 deletions
|
@ -79,6 +79,11 @@ module.exports = class LevelLoader extends CocoClass
|
||||||
@listenToOnce @level, 'sync', @onLevelLoaded
|
@listenToOnce @level, 'sync', @onLevelLoaded
|
||||||
|
|
||||||
reportLoadError: ->
|
reportLoadError: ->
|
||||||
|
window.tracker?.trackEvent 'LevelLoadError',
|
||||||
|
category: 'Error',
|
||||||
|
levelSlug: @work?.level?.slug,
|
||||||
|
unloaded: JSON.stringify(@supermodel.report().map (m) -> _.result(m.model, 'url'))
|
||||||
|
|
||||||
return if me.isAdmin() or /dev=true/.test(window.location?.href ? '') or reportedLoadErrorAlready
|
return if me.isAdmin() or /dev=true/.test(window.location?.href ? '') or reportedLoadErrorAlready
|
||||||
reportedLoadErrorAlready = true
|
reportedLoadErrorAlready = true
|
||||||
context = email: me.get('email')
|
context = email: me.get('email')
|
||||||
|
|
|
@ -243,7 +243,7 @@ module.exports = nativeDescription: "Türkçe", englishDescription: "Turkish", t
|
||||||
|
|
||||||
login:
|
login:
|
||||||
sign_up: "Hesap Oluştur"
|
sign_up: "Hesap Oluştur"
|
||||||
# email_or_username: "Email or username"
|
email_or_username: "E-posta veya kullanıcı adı"
|
||||||
log_in: "Giriş Yap"
|
log_in: "Giriş Yap"
|
||||||
logging_in: "Giriş Yapılıyor"
|
logging_in: "Giriş Yapılıyor"
|
||||||
log_out: "Çıkış Yap"
|
log_out: "Çıkış Yap"
|
||||||
|
@ -256,13 +256,13 @@ module.exports = nativeDescription: "Türkçe", englishDescription: "Turkish", t
|
||||||
signup_switch: "Hesap oluşturmak istiyor musun?"
|
signup_switch: "Hesap oluşturmak istiyor musun?"
|
||||||
|
|
||||||
signup:
|
signup:
|
||||||
# create_student_header: "Create Student Account"
|
create_student_header: "Öğrenci Hesabı Oluştur"
|
||||||
# create_teacher_header: "Create Teacher Account"
|
create_teacher_header: "Öğretmen Hesabı Oluştur"
|
||||||
# create_individual_header: "Create Individual Account"
|
create_individual_header: "Bireysel Hesap Oluştur"
|
||||||
# create_header: "Create Account"
|
create_header: "Hesap Oluştur"
|
||||||
email_announcements: "E-posta duyurularını almak istiyorum" # {change}
|
email_announcements: "E-posta duyurularını almak istiyorum" # {change}
|
||||||
creating: "Hesap oluşturuluyor..."
|
creating: "Hesap oluşturuluyor..."
|
||||||
# create_account: "Create Account"
|
create_account: "Hesap Oluştur"
|
||||||
sign_up: "Kaydol"
|
sign_up: "Kaydol"
|
||||||
log_in: "buradan giriş yapabilirsiniz."
|
log_in: "buradan giriş yapabilirsiniz."
|
||||||
required: "Buraya gidebilmeniz için oturum açmanız gerekli."
|
required: "Buraya gidebilmeniz için oturum açmanız gerekli."
|
||||||
|
@ -278,46 +278,46 @@ module.exports = nativeDescription: "Türkçe", englishDescription: "Turkish", t
|
||||||
connected_facebook_p: "Kayıt işlemini bitir, artık Facebook hesabınla giriş yapabilirsin."
|
connected_facebook_p: "Kayıt işlemini bitir, artık Facebook hesabınla giriş yapabilirsin."
|
||||||
facebook_exists: "Zaten Facebook ile ilişkilendirilmiş bir hesabın bulunuyor!"
|
facebook_exists: "Zaten Facebook ile ilişkilendirilmiş bir hesabın bulunuyor!"
|
||||||
hey_students: "Öğrenciler, öğretmeninizin verdiği sınıf kodunu girin."
|
hey_students: "Öğrenciler, öğretmeninizin verdiği sınıf kodunu girin."
|
||||||
# birthday: "Birthday"
|
birthday: "Doğum günü"
|
||||||
# parent_email_blurb: "We know you can't wait to learn programming — we're excited too! Your parents will receive an email with further instructions on how to create an account for you. Email {{email_link}} if you have any questions."
|
# parent_email_blurb: "We know you can't wait to learn programming — we're excited too! Your parents will receive an email with further instructions on how to create an account for you. Email {{email_link}} if you have any questions."
|
||||||
# classroom_not_found: "No classes exist with this Class Code. Check your spelling or ask your teacher for help."
|
# classroom_not_found: "No classes exist with this Class Code. Check your spelling or ask your teacher for help."
|
||||||
# checking: "Checking..."
|
checking: "Kontrol ediliyor..."
|
||||||
# account_exists: "This email is already in use:" # {change}
|
# account_exists: "This email is already in use:" # {change}
|
||||||
# sign_in: "Sign in"
|
sign_in: "Oturum aç"
|
||||||
# email_good: "Email looks good!"
|
email_good: "E-posta iyi görünüyor!"
|
||||||
# name_taken: "Username already taken! Try {{suggestedName}}?"
|
# name_taken: "Username already taken! Try {{suggestedName}}?"
|
||||||
# name_available: "Username available!"
|
name_available: "Kullanıcı adı müsait!"
|
||||||
# name_is_email: "Username may not be an email"
|
# name_is_email: "Username may not be an email"
|
||||||
# choose_type: "Choose your account type:"
|
choose_type: "Hesabınızın türünü seçin:"
|
||||||
# teacher_type_1: "Teach programming using CodeCombat!"
|
# teacher_type_1: "Teach programming using CodeCombat!"
|
||||||
# teacher_type_2: "Set up your class"
|
# teacher_type_2: "Set up your class"
|
||||||
# teacher_type_3: "Access Course Guides"
|
# teacher_type_3: "Access Course Guides"
|
||||||
# teacher_type_4: "View student progress"
|
# teacher_type_4: "View student progress"
|
||||||
# signup_as_teacher: "Sign up as a Teacher"
|
signup_as_teacher: "Öğretmen olarak Kaydol"
|
||||||
# student_type_1: "Learn to program while playing an engaging game!"
|
student_type_1: "İlgi çekici bir oyun oynarken program öğren!"
|
||||||
# student_type_2: "Play with your class"
|
student_type_2: "Sınıfınla oyna"
|
||||||
# student_type_3: "Compete in arenas"
|
student_type_3: "Arenalarda rekabet et"
|
||||||
# student_type_4: "Choose your hero!"
|
student_type_4: "Kahramanını seç!"
|
||||||
# student_type_5: "Have your Class Code ready!"
|
# student_type_5: "Have your Class Code ready!"
|
||||||
# signup_as_student: "Sign up as a Student"
|
signup_as_student: "Öğrenci olarak Kaydol"
|
||||||
# individuals_or_parents: "Individuals & Parents"
|
# individuals_or_parents: "Individuals & Parents"
|
||||||
# individual_type: "For players learning to code outside of a class. Parents should sign up for an account here."
|
# individual_type: "For players learning to code outside of a class. Parents should sign up for an account here."
|
||||||
# signup_as_individual: "Sign up as an Individual"
|
signup_as_individual: "Bireysel olarak Kaydol"
|
||||||
# enter_class_code: "Enter your Class Code"
|
enter_class_code: "Sınıf Kodunu Gir"
|
||||||
# enter_birthdate: "Enter your birthdate:"
|
enter_birthdate: "Doğum tarihini gir:"
|
||||||
# ask_teacher_1: "Ask your teacher for your Class Code."
|
ask_teacher_1: "Sınıf kodun için öğretmenine sor."
|
||||||
# ask_teacher_2: "Not part of a class? Create an "
|
# ask_teacher_2: "Not part of a class? Create an "
|
||||||
# ask_teacher_3: "Individual Account"
|
ask_teacher_3: "Bireysel Hesap"
|
||||||
# ask_teacher_4: " instead."
|
# ask_teacher_4: " instead."
|
||||||
# about_to_join: "You're about to join:"
|
# about_to_join: "You're about to join:"
|
||||||
# enter_parent_email: "Enter your parent’s email address:"
|
# enter_parent_email: "Enter your parent’s email address:"
|
||||||
# parent_email_error: "Something went wrong when trying to send the email. Check the email address and try again."
|
# parent_email_error: "Something went wrong when trying to send the email. Check the email address and try again."
|
||||||
# parent_email_sent: "We’ve sent an email with further instructions on how to create an account. Ask your parent to check their inbox."
|
# parent_email_sent: "We’ve sent an email with further instructions on how to create an account. Ask your parent to check their inbox."
|
||||||
# account_created: "Account Created!"
|
account_created: "Hesap Oluşturuldu!"
|
||||||
# confirm_student_blurb: "Write down your information so that you don't forget it. Your teacher can also help you reset your password at any time."
|
# confirm_student_blurb: "Write down your information so that you don't forget it. Your teacher can also help you reset your password at any time."
|
||||||
# confirm_individual_blurb: "Write down your login information in case you need it later. Verify your email so you can recover your account if you ever forget your password - check your inbox!"
|
# confirm_individual_blurb: "Write down your login information in case you need it later. Verify your email so you can recover your account if you ever forget your password - check your inbox!"
|
||||||
# write_this_down: "Write this down:"
|
# write_this_down: "Write this down:"
|
||||||
# start_playing: "Start Playing!"
|
start_playing: "Oynamaya Başla!"
|
||||||
# sso_connected: "Successfully connected with:"
|
# sso_connected: "Successfully connected with:"
|
||||||
|
|
||||||
recover:
|
recover:
|
||||||
|
|
|
@ -309,6 +309,8 @@ class CocoModel extends Backbone.Model
|
||||||
sum = 0
|
sum = 0
|
||||||
data ?= $.extend true, {}, @attributes
|
data ?= $.extend true, {}, @attributes
|
||||||
schema ?= @schema() or {}
|
schema ?= @schema() or {}
|
||||||
|
if schema.oneOf # get populating the Programmable component config to work
|
||||||
|
schema = _.find(schema.oneOf, {type: 'object'})
|
||||||
addedI18N = false
|
addedI18N = false
|
||||||
if schema.properties?.i18n and _.isPlainObject(data) and not data.i18n?
|
if schema.properties?.i18n and _.isPlainObject(data) and not data.i18n?
|
||||||
data.i18n = {'-':{'-':'-'}} # mongoose doesn't work with empty objects
|
data.i18n = {'-':{'-':'-'}} # mongoose doesn't work with empty objects
|
||||||
|
@ -318,7 +320,11 @@ class CocoModel extends Backbone.Model
|
||||||
if _.isPlainObject data
|
if _.isPlainObject data
|
||||||
for key, value of data
|
for key, value of data
|
||||||
numChanged = 0
|
numChanged = 0
|
||||||
numChanged = @populateI18N(value, childSchema, path+'/'+key) if childSchema = schema.properties?[key]
|
childSchema = schema.properties?[key]
|
||||||
|
if not childSchema and _.isObject(schema.additionalProperties)
|
||||||
|
childSchema = schema.additionalProperties
|
||||||
|
if childSchema
|
||||||
|
numChanged = @populateI18N(value, childSchema, path+'/'+key)
|
||||||
if numChanged and not path # should only do this for the root object
|
if numChanged and not path # should only do this for the root object
|
||||||
@set key, value
|
@set key, value
|
||||||
sum += numChanged
|
sum += numChanged
|
||||||
|
|
|
@ -164,7 +164,6 @@
|
||||||
content: " "
|
content: " "
|
||||||
display: inline-block
|
display: inline-block
|
||||||
position: relative
|
position: relative
|
||||||
left: -49px
|
|
||||||
width: 49px
|
width: 49px
|
||||||
top: -30px
|
top: -30px
|
||||||
height: 38px
|
height: 38px
|
||||||
|
@ -172,6 +171,20 @@
|
||||||
|
|
||||||
background-image: url(data:image/png;base64,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)
|
background-image: url(data:image/png;base64,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)
|
||||||
|
|
||||||
|
.ace_gutter-cell.entry-point:not(.next-entry-point):after
|
||||||
|
opacity: 0.5
|
||||||
|
|
||||||
|
.ace_gutter-cell.entry-point.entry-point-indent-0:after
|
||||||
|
left: -25px
|
||||||
|
.ace_gutter-cell.entry-point.entry-point-indent-4:after
|
||||||
|
left: 5px
|
||||||
|
.ace_gutter-cell.entry-point.entry-point-indent-8:after
|
||||||
|
left: 33px
|
||||||
|
.ace_gutter-cell.entry-point.entry-point-indent-12:after
|
||||||
|
left: 61px
|
||||||
|
.ace_gutter-cell.entry-point.entry-point-indent-16:after
|
||||||
|
left: 89px
|
||||||
|
|
||||||
.ace_gutter-cell.ace_error
|
.ace_gutter-cell.ace_error
|
||||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTM5jWRgMAAAAVdEVYdENyZWF0aW9uIFRpbWUAMi8xNy8wOCCcqlgAAAQRdEVYdFhNTDpjb20uYWRvYmUueG1wADw/eHBhY2tldCBiZWdpbj0iICAgIiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+Cjx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDQuMS1jMDM0IDQ2LjI3Mjk3NiwgU2F0IEphbiAyNyAyMDA3IDIyOjExOjQxICAgICAgICAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp4YXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iPgogICAgICAgICA8eGFwOkNyZWF0b3JUb29sPkFkb2JlIEZpcmV3b3JrcyBDUzM8L3hhcDpDcmVhdG9yVG9vbD4KICAgICAgICAgPHhhcDpDcmVhdGVEYXRlPjIwMDgtMDItMTdUMDI6MzY6NDVaPC94YXA6Q3JlYXRlRGF0ZT4KICAgICAgICAgPHhhcDpNb2RpZnlEYXRlPjIwMDgtMDMtMjRUMTk6MDA6NDJaPC94YXA6TW9kaWZ5RGF0ZT4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyI+CiAgICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2UvcG5nPC9kYzpmb3JtYXQ+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDUdUmQAAAD5SURBVDiNpZMxagMxEEWfgiCXcB3IbXwD7zbaM0nNyjdIl1O4Dk7pbsslEFbEZFKsJsiJrGDy4YM0M//zRyAoINAJyB8cS43RwwIdMFrvaeE8DADxXqQ3Jstn6GaQ5L3M0GQxsyaZoJtA3r2XCS6o+FkvZkdOIG/eywl+UVHrqcYm4BNIjb1rPdXYBTivj3gVtZ5q/p8gAfPhcLOBamzKcW41UI1dgA/qez4bU6muUE0zwVYEgKeKkWruEnTHENg4R8pFZblCyY1zHEMgQTQAe9gB8cE5XkO4GhugmIk76L+z+Wzy6FzT4CWLXf5MF8upSdMB4gC9Xr4AiezTJHGxdq0AAAAASUVORK5CYII=)
|
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTM5jWRgMAAAAVdEVYdENyZWF0aW9uIFRpbWUAMi8xNy8wOCCcqlgAAAQRdEVYdFhNTDpjb20uYWRvYmUueG1wADw/eHBhY2tldCBiZWdpbj0iICAgIiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+Cjx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDQuMS1jMDM0IDQ2LjI3Mjk3NiwgU2F0IEphbiAyNyAyMDA3IDIyOjExOjQxICAgICAgICAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp4YXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iPgogICAgICAgICA8eGFwOkNyZWF0b3JUb29sPkFkb2JlIEZpcmV3b3JrcyBDUzM8L3hhcDpDcmVhdG9yVG9vbD4KICAgICAgICAgPHhhcDpDcmVhdGVEYXRlPjIwMDgtMDItMTdUMDI6MzY6NDVaPC94YXA6Q3JlYXRlRGF0ZT4KICAgICAgICAgPHhhcDpNb2RpZnlEYXRlPjIwMDgtMDMtMjRUMTk6MDA6NDJaPC94YXA6TW9kaWZ5RGF0ZT4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyI+CiAgICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2UvcG5nPC9kYzpmb3JtYXQ+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDUdUmQAAAD5SURBVDiNpZMxagMxEEWfgiCXcB3IbXwD7zbaM0nNyjdIl1O4Dk7pbsslEFbEZFKsJsiJrGDy4YM0M//zRyAoINAJyB8cS43RwwIdMFrvaeE8DADxXqQ3Jstn6GaQ5L3M0GQxsyaZoJtA3r2XCS6o+FkvZkdOIG/eywl+UVHrqcYm4BNIjb1rPdXYBTivj3gVtZ5q/p8gAfPhcLOBamzKcW41UI1dgA/qez4bU6muUE0zwVYEgKeKkWruEnTHENg4R8pFZblCyY1zHEMgQTQAe9gB8cE5XkO4GhugmIk76L+z+Wzy6FzT4CWLXf5MF8upSdMB4gC9Xr4AiezTJHGxdq0AAAAASUVORK5CYII=)
|
||||||
|
|
||||||
|
@ -179,9 +192,6 @@
|
||||||
.ace_gutter-cell.ace_info
|
.ace_gutter-cell.ace_info
|
||||||
background-image: none
|
background-image: none
|
||||||
|
|
||||||
.ace_gutter-cell.entry-point:not(.next-entry-point):after
|
|
||||||
opacity: 0.5
|
|
||||||
|
|
||||||
.ace_marker-layer
|
.ace_marker-layer
|
||||||
.ace_bracket
|
.ace_bracket
|
||||||
// Override faint gray
|
// Override faint gray
|
||||||
|
|
|
@ -2,6 +2,7 @@ RootView = require 'views/core/RootView'
|
||||||
template = require 'templates/editor/level/edit'
|
template = require 'templates/editor/level/edit'
|
||||||
Level = require 'models/Level'
|
Level = require 'models/Level'
|
||||||
LevelSystem = require 'models/LevelSystem'
|
LevelSystem = require 'models/LevelSystem'
|
||||||
|
LevelComponent = require 'models/LevelComponent'
|
||||||
World = require 'lib/world/world'
|
World = require 'lib/world/world'
|
||||||
DocumentFiles = require 'collections/DocumentFiles'
|
DocumentFiles = require 'collections/DocumentFiles'
|
||||||
LevelLoader = require 'lib/LevelLoader'
|
LevelLoader = require 'lib/LevelLoader'
|
||||||
|
@ -217,6 +218,19 @@ module.exports = class LevelEditView extends RootView
|
||||||
|
|
||||||
onPopulateI18N: ->
|
onPopulateI18N: ->
|
||||||
@level.populateI18N()
|
@level.populateI18N()
|
||||||
|
|
||||||
|
levelComponentMap = _(currentView.supermodel.getModels(LevelComponent))
|
||||||
|
.map((c) -> [c.get('original'), c])
|
||||||
|
.object()
|
||||||
|
.value()
|
||||||
|
|
||||||
|
for thang, thangIndex in @level.get('thangs')
|
||||||
|
for thangComponent, thangComponentIndex in thang.components
|
||||||
|
component = levelComponentMap[thangComponent.original]
|
||||||
|
configSchema = component.get('configSchema')
|
||||||
|
path = "/thangs/#{thangIndex}/components/#{thangComponentIndex}/config"
|
||||||
|
@level.populateI18N(thangComponent.config, configSchema, path)
|
||||||
|
|
||||||
f = -> document.location.reload()
|
f = -> document.location.reload()
|
||||||
setTimeout(f, 2000)
|
setTimeout(f, 2000)
|
||||||
|
|
||||||
|
|
|
@ -641,25 +641,7 @@ module.exports = class PlayLevelView extends RootView
|
||||||
# Real-time playback
|
# Real-time playback
|
||||||
onRealTimePlaybackStarted: (e) ->
|
onRealTimePlaybackStarted: (e) ->
|
||||||
@$el.addClass('real-time').focus()
|
@$el.addClass('real-time').focus()
|
||||||
if @level.isType('game-dev')
|
@$('#how-to-play-game-dev-panel').removeClass('hide') if @level.isType('game-dev')
|
||||||
panel = @$('#how-to-play-game-dev-panel')
|
|
||||||
panel.removeClass('hide')
|
|
||||||
# TODO: Remove this once these levels have studentPlayInstructions set.
|
|
||||||
if not @level.get('studentPlayInstructions')
|
|
||||||
lines = switch @level.get('slug')
|
|
||||||
when 'over-the-garden-wall' then ['Watch to see if the peasants are properly protected.']
|
|
||||||
when 'click-gait' then ['Move to each red "X".', 'Click on the screen to move the Knight there.']
|
|
||||||
when 'heros-journey' then ['Move to each red "X".', 'Click on the screen to move the Knight there.']
|
|
||||||
when 'a-maze-ing' then ['Move to the chest of gems.', 'Click on the screen to move the Duelist there.']
|
|
||||||
when 'gemtacular' then ['Move to each of the gems.', 'Click on the screen to move the Captain there.']
|
|
||||||
when 'vorpal-mouse' then ['Slay the ogres.', 'Click on the screen to move the Guardian there.', 'Click on the munchkins to attack them!']
|
|
||||||
when 'crushing-it' then ['Slay the ogres.', 'Click on the screen to move the Goliath there.', 'Click on the munchkins to attack them!']
|
|
||||||
when 'tabula-rasa' then ['Slay any ogres.', 'Collect any coins.', 'Click on the screen to move the Raider there.', 'Click on any munchkins to attack them!']
|
|
||||||
else null
|
|
||||||
if lines
|
|
||||||
html = _.map(lines, (line) -> "<p>#{line}</p>").join('')
|
|
||||||
panel.find('.panel-body').html(html)
|
|
||||||
|
|
||||||
@onWindowResize()
|
@onWindowResize()
|
||||||
|
|
||||||
onRealTimePlaybackEnded: (e) ->
|
onRealTimePlaybackEnded: (e) ->
|
||||||
|
|
|
@ -803,7 +803,7 @@ module.exports = class SpellView extends CocoView
|
||||||
# This function itself removes the unwanted annotations on a later tick.
|
# This function itself removes the unwanted annotations on a later tick.
|
||||||
onChangeAnnotation: (event, session) ->
|
onChangeAnnotation: (event, session) ->
|
||||||
unfilteredAnnotations = session.getAnnotations()
|
unfilteredAnnotations = session.getAnnotations()
|
||||||
filteredAnnotations = _.remove unfilteredAnnotations, (annotation) ->
|
filteredAnnotations = _.reject unfilteredAnnotations, (annotation) ->
|
||||||
annotation.text is 'Start tag seen without seeing a doctype first. Expected e.g. <!DOCTYPE html>.'
|
annotation.text is 'Start tag seen without seeing a doctype first. Expected e.g. <!DOCTYPE html>.'
|
||||||
if filteredAnnotations.length < unfilteredAnnotations.length
|
if filteredAnnotations.length < unfilteredAnnotations.length
|
||||||
session.setAnnotations(filteredAnnotations)
|
session.setAnnotations(filteredAnnotations)
|
||||||
|
@ -1113,6 +1113,7 @@ module.exports = class SpellView extends CocoView
|
||||||
for line, index in lines
|
for line, index in lines
|
||||||
session.removeGutterDecoration index, 'entry-point'
|
session.removeGutterDecoration index, 'entry-point'
|
||||||
session.removeGutterDecoration index, 'next-entry-point'
|
session.removeGutterDecoration index, 'next-entry-point'
|
||||||
|
session.removeGutterDecoration index, "entry-point-indent-#{i}" for i in [0, 4, 8, 12, 16]
|
||||||
|
|
||||||
lineHasComment = @singleLineCommentRegex().test line
|
lineHasComment = @singleLineCommentRegex().test line
|
||||||
lineHasCode = line.trim()[0] and not @singleLineCommentOnlyRegex().test line
|
lineHasCode = line.trim()[0] and not @singleLineCommentOnlyRegex().test line
|
||||||
|
@ -1146,6 +1147,13 @@ module.exports = class SpellView extends CocoView
|
||||||
session.addGutterDecoration index, 'next-entry-point'
|
session.addGutterDecoration index, 'next-entry-point'
|
||||||
seenAnEntryPoint = true
|
seenAnEntryPoint = true
|
||||||
|
|
||||||
|
# Shift pointer right based on current indentation
|
||||||
|
# TODO: tabs probably need different horizontal offsets than spaces
|
||||||
|
indent = 0
|
||||||
|
indent++ while /\s/.test(line[indent])
|
||||||
|
indent = Math.min(16, Math.floor(indent / 4) * 4)
|
||||||
|
session.addGutterDecoration index, "entry-point-indent-#{indent}"
|
||||||
|
|
||||||
previousLine = line
|
previousLine = line
|
||||||
previousLineHadComment = lineHasComment
|
previousLineHadComment = lineHasComment
|
||||||
previousLineHadCode = lineHasCode
|
previousLineHadCode = lineHasCode
|
||||||
|
|
167
scripts/node/fixEmailFormattedUsernames.coffee
Normal file
167
scripts/node/fixEmailFormattedUsernames.coffee
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
# Usage:
|
||||||
|
# > coffee -c scripts/node/fixEmailFormattedUsernames.coffee; node scripts/node/fixEmailFormattedUsernames.js run
|
||||||
|
|
||||||
|
require('coffee-script');
|
||||||
|
require('coffee-script/register');
|
||||||
|
|
||||||
|
_ = require 'lodash'
|
||||||
|
sendwithus = require '../../server/sendwithus'
|
||||||
|
log = require 'winston'
|
||||||
|
str = require 'underscore.string'
|
||||||
|
co = require 'co'
|
||||||
|
|
||||||
|
filter = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,63}$/i
|
||||||
|
|
||||||
|
changedUsernameTemplate = _.template("
|
||||||
|
<p>
|
||||||
|
Hi, CodeCombat user!
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Just letting you know we've made a change to your account settings which may change how you log in. Here are your old settings:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Old username: <%= oldUsername %></li>
|
||||||
|
<li>Old email: <%= oldEmail %></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</p>
|
||||||
|
Your old username was an email address, but to reduce confusion, we now make sure email addresses can't be used as usernames.
|
||||||
|
<b><%= specialMessage %></b>
|
||||||
|
Here are your new settings:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>New username: <%= newUsername %></li>
|
||||||
|
<li>New email: <%= newEmail %></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Please <a href='https://codecombat.com/account/settings'>visit the site</a> if you would like to update your settings.
|
||||||
|
And let us know if you have any questions!
|
||||||
|
</p>
|
||||||
|
")
|
||||||
|
|
||||||
|
exports.run = ->
|
||||||
|
co ->
|
||||||
|
mongoose = require 'mongoose'
|
||||||
|
User = require '../../server/models/User'
|
||||||
|
users = yield User.find({nameLower: {$regex: filter}}).select({name:1, email:1, anonymous:1, slug:1})
|
||||||
|
console.log 'found', users.length, 'users'
|
||||||
|
|
||||||
|
for user in users
|
||||||
|
oldUsername = user.get('name')
|
||||||
|
oldEmail = user.get('email')
|
||||||
|
newUsername = null
|
||||||
|
newEmail = null
|
||||||
|
specialMessage = ''
|
||||||
|
|
||||||
|
if not oldEmail
|
||||||
|
otherUser = yield User.findByEmail(oldUsername)
|
||||||
|
if otherUser
|
||||||
|
specialMessage = "Since you had no email set, we would have made your old username your new email.
|
||||||
|
But '#{oldUsername}' is already used by another account as an email by another account,
|
||||||
|
so instead we changed your username."
|
||||||
|
newUsername = str.slugify(oldUsername)
|
||||||
|
newEmail = ''
|
||||||
|
|
||||||
|
else
|
||||||
|
specialMessage = "Since you had no email set, we simply made your old username your new email instead."
|
||||||
|
newEmail = oldUsername
|
||||||
|
newUsername = ''
|
||||||
|
|
||||||
|
|
||||||
|
else if oldEmail is oldUsername
|
||||||
|
specialMessage = "Since your email and username are the same, we simply removed your username."
|
||||||
|
newUsername = ''
|
||||||
|
newEmail = oldEmail
|
||||||
|
|
||||||
|
|
||||||
|
else if not filter.test(oldEmail)
|
||||||
|
otherEmailUser = yield User.findByEmail(oldUsername)
|
||||||
|
otherUsernameUser = yield User.findByName(oldEmail)
|
||||||
|
if otherEmailUser
|
||||||
|
specialMessage = "Since your old email looks like a username and your old username looks like an email,
|
||||||
|
we would have swapped them on your account.
|
||||||
|
But '#{oldUsername}' is already used as an email by another account,
|
||||||
|
so instead we changed your username."
|
||||||
|
newUsername = str.slugify(oldUsername)
|
||||||
|
newEmail = oldEmail
|
||||||
|
|
||||||
|
else if otherUsernameUser
|
||||||
|
specialMessage = "Since your old email looks like a username and your old username looks like an email,
|
||||||
|
we would have swapped them on your account.
|
||||||
|
But '#{oldEmail}' is already used as a username by another account,
|
||||||
|
so instead we changed your username."
|
||||||
|
newUsername = str.slugify(oldUsername)
|
||||||
|
newEmail = oldEmail
|
||||||
|
else
|
||||||
|
specialMessage = "Since your old email looks like a username and your old username looks like an email,
|
||||||
|
we swapped them on your account."
|
||||||
|
newUsername = oldEmail
|
||||||
|
newEmail = oldUsername
|
||||||
|
|
||||||
|
|
||||||
|
else if oldUsername and oldEmail
|
||||||
|
# Since oldEmail passed the email filter,
|
||||||
|
specialMessage = "Since your old email is valid, we simply removed your username."
|
||||||
|
newUsername = ''
|
||||||
|
newEmail = oldEmail
|
||||||
|
|
||||||
|
|
||||||
|
else
|
||||||
|
console.log('unhandled user', user.toObject())
|
||||||
|
throw new Error('Unhandled user')
|
||||||
|
|
||||||
|
user.set({name: newUsername, email: newEmail})
|
||||||
|
console.log JSON.stringify({
|
||||||
|
oldUsername, oldEmail, newUsername, newEmail, specialMessage, _id: user.id
|
||||||
|
})
|
||||||
|
yield user.save()
|
||||||
|
|
||||||
|
content = changedUsernameTemplate({
|
||||||
|
oldUsername: oldUsername or '<i>(no username)</i>'
|
||||||
|
oldEmail: oldEmail or '<i>(no email)</i>'
|
||||||
|
newUsername: newUsername or '<i>(no username)</i>'
|
||||||
|
newEmail: newEmail or '<i>(no email)</i>'
|
||||||
|
specialMessage
|
||||||
|
})
|
||||||
|
|
||||||
|
context =
|
||||||
|
template: sendwithus.templates.plain_text_email
|
||||||
|
recipient:
|
||||||
|
address: oldUsername
|
||||||
|
sender:
|
||||||
|
address: 'team@codecombat.com'
|
||||||
|
name: 'CodeCombat Team'
|
||||||
|
template_data:
|
||||||
|
subject: 'Your Username Has Changed'
|
||||||
|
contentHTML: content
|
||||||
|
|
||||||
|
# Also send to the original email if it's valid
|
||||||
|
if filter.test(oldEmail)
|
||||||
|
context.cc = [
|
||||||
|
{ address: oldEmail }
|
||||||
|
]
|
||||||
|
|
||||||
|
yield sendwithus.api.sendAsync(context)
|
||||||
|
|
||||||
|
return 'Done'
|
||||||
|
|
||||||
|
if _.last(process.argv) is 'run'
|
||||||
|
database = require '../../server/commons/database'
|
||||||
|
mongoose = require 'mongoose'
|
||||||
|
|
||||||
|
### SET UP ###
|
||||||
|
do (setupLodash = this) ->
|
||||||
|
GLOBAL._ = require 'lodash'
|
||||||
|
_.str = require 'underscore.string'
|
||||||
|
_.mixin _.str.exports()
|
||||||
|
GLOBAL.tv4 = require('tv4').tv4
|
||||||
|
|
||||||
|
database.connect()
|
||||||
|
co ->
|
||||||
|
yield exports.run()
|
||||||
|
process.exit()
|
||||||
|
|
|
@ -10,6 +10,7 @@ Classroom = require '../models/Classroom'
|
||||||
languages = require '../routes/languages'
|
languages = require '../routes/languages'
|
||||||
_ = require 'lodash'
|
_ = require 'lodash'
|
||||||
errors = require '../commons/errors'
|
errors = require '../commons/errors'
|
||||||
|
Promise = require 'bluebird'
|
||||||
|
|
||||||
config = require '../../server_config'
|
config = require '../../server_config'
|
||||||
stripe = require('stripe')(config.stripe.secretKey)
|
stripe = require('stripe')(config.stripe.secretKey)
|
||||||
|
@ -270,6 +271,8 @@ UserSchema.statics.unconflictName = unconflictName = (name, done) ->
|
||||||
suffix = _.random(0, 9) + ''
|
suffix = _.random(0, 9) + ''
|
||||||
unconflictName name + suffix, done
|
unconflictName name + suffix, done
|
||||||
|
|
||||||
|
UserSchema.statics.unconflictNameAsync = Promise.promisify(unconflictName)
|
||||||
|
|
||||||
UserSchema.methods.sendWelcomeEmail = ->
|
UserSchema.methods.sendWelcomeEmail = ->
|
||||||
return if not @get('email')
|
return if not @get('email')
|
||||||
{ welcome_email_student, welcome_email_user } = sendwithus.templates
|
{ welcome_email_student, welcome_email_user } = sendwithus.templates
|
||||||
|
@ -361,9 +364,10 @@ UserSchema.pre('save', (next) ->
|
||||||
@set('email', undefined)
|
@set('email', undefined)
|
||||||
@set('emailLower', undefined)
|
@set('emailLower', undefined)
|
||||||
if name = @get('name')
|
if name = @get('name')
|
||||||
filter = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,63}$/i # https://news.ycombinator.com/item?id=5763990
|
if not @allowEmailNames # for testing
|
||||||
if filter.test(name)
|
filter = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,63}$/i # https://news.ycombinator.com/item?id=5763990
|
||||||
return next(new errors.UnprocessableEntity('Name may not be an email'))
|
if filter.test(name)
|
||||||
|
return next(new errors.UnprocessableEntity('Name may not be an email'))
|
||||||
|
|
||||||
@set('nameLower', name.toLowerCase())
|
@set('nameLower', name.toLowerCase())
|
||||||
else
|
else
|
||||||
|
|
|
@ -2,6 +2,7 @@ config = require '../server_config'
|
||||||
sendwithusAPI = require 'sendwithus'
|
sendwithusAPI = require 'sendwithus'
|
||||||
swuAPIKey = config.mail.sendwithusAPIKey
|
swuAPIKey = config.mail.sendwithusAPIKey
|
||||||
log = require 'winston'
|
log = require 'winston'
|
||||||
|
Promise = require 'bluebird'
|
||||||
|
|
||||||
module.exports.setupRoutes = (app) ->
|
module.exports.setupRoutes = (app) ->
|
||||||
return
|
return
|
||||||
|
@ -15,6 +16,8 @@ module.exports.api =
|
||||||
if swuAPIKey
|
if swuAPIKey
|
||||||
module.exports.api = new sendwithusAPI swuAPIKey, debug
|
module.exports.api = new sendwithusAPI swuAPIKey, debug
|
||||||
|
|
||||||
|
Promise.promisifyAll(module.exports.api)
|
||||||
|
|
||||||
module.exports.templates =
|
module.exports.templates =
|
||||||
parent_subscribe_email: 'tem_2APERafogvwKhmcnouigud'
|
parent_subscribe_email: 'tem_2APERafogvwKhmcnouigud'
|
||||||
coppa_deny_parent_signup: 'tem_d5fCpXS8V7jgff2sYKCinX'
|
coppa_deny_parent_signup: 'tem_d5fCpXS8V7jgff2sYKCinX'
|
||||||
|
|
115
spec/server/scripts/fixEmailFormattedUsernames.spec.coffee
Normal file
115
spec/server/scripts/fixEmailFormattedUsernames.spec.coffee
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
# Don't need to run this regularly, only running script once.
|
||||||
|
|
||||||
|
#utils = require '../utils'
|
||||||
|
#User = require '../../../server/models/User'
|
||||||
|
#request = require '../request'
|
||||||
|
#sendwithus = require '../../../server/sendwithus'
|
||||||
|
#fixEmailFormattedUsernames = require '../../../scripts/node/fixEmailFormattedUsernames'
|
||||||
|
#
|
||||||
|
#describe '/scripts/node/fixEmailFormattedUsernames', ->
|
||||||
|
#
|
||||||
|
# beforeEach utils.wrap (done) ->
|
||||||
|
# yield utils.clearModels([User])
|
||||||
|
# console.log('spy on send async')
|
||||||
|
# spyOn(sendwithus.api, 'sendAsync').and.callThrough()
|
||||||
|
# done()
|
||||||
|
#
|
||||||
|
# afterEach ->
|
||||||
|
# expect(sendwithus.api.sendAsync).toHaveBeenCalled()
|
||||||
|
#
|
||||||
|
# describe "when a user has no email set", ->
|
||||||
|
# beforeEach utils.wrap (done) ->
|
||||||
|
# @user = new User({name: 'an@email.com', points:100})
|
||||||
|
# @user.allowEmailNames = true
|
||||||
|
# yield @user.save()
|
||||||
|
# done()
|
||||||
|
#
|
||||||
|
# it 'moves the email-formatted username to be the user\'s email', utils.wrap (done) ->
|
||||||
|
# yield fixEmailFormattedUsernames.run()
|
||||||
|
# user = yield User.findById(@user.id)
|
||||||
|
# expect(user.get('email')).toBe('an@email.com')
|
||||||
|
# expect(user.get('name')).toBeUndefined()
|
||||||
|
# expect(user.get('points')).toBe(100) # make sure properties aren't removed
|
||||||
|
# done()
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# describe "when another user exists with that email", ->
|
||||||
|
# beforeEach utils.wrap (done) ->
|
||||||
|
# @otherUser = new User({email: 'an@email.com'})
|
||||||
|
# yield @otherUser.save()
|
||||||
|
# done()
|
||||||
|
#
|
||||||
|
# it "slugifies the target user's username", utils.wrap (done) ->
|
||||||
|
# yield fixEmailFormattedUsernames.run()
|
||||||
|
# user = yield User.findById(@user.id)
|
||||||
|
# expect(user.get('email')).toBeUndefined()
|
||||||
|
# expect(user.get('name')).toBe('anemailcom')
|
||||||
|
# done()
|
||||||
|
#
|
||||||
|
# describe "when a user has the same email and username", ->
|
||||||
|
# beforeEach utils.wrap (done) ->
|
||||||
|
# @user = new User({name: 'an@email.com', email: 'an@email.com'})
|
||||||
|
# @user.allowEmailNames = true
|
||||||
|
# yield @user.save()
|
||||||
|
# done()
|
||||||
|
#
|
||||||
|
# it "removes the user's username", utils.wrap (done) ->
|
||||||
|
# yield fixEmailFormattedUsernames.run()
|
||||||
|
# user = yield User.findById(@user.id)
|
||||||
|
# expect(user.get('email')).toBe('an@email.com')
|
||||||
|
# expect(user.get('name')).toBeUndefined()
|
||||||
|
# done()
|
||||||
|
#
|
||||||
|
# describe "when the user has an email that isn't formatted like an email", ->
|
||||||
|
# beforeEach utils.wrap (done) ->
|
||||||
|
# @user = new User({name: 'an@email.com', email: 'a name'})
|
||||||
|
# @user.allowEmailNames = true
|
||||||
|
# yield @user.save()
|
||||||
|
# done()
|
||||||
|
#
|
||||||
|
# it "swaps the two", utils.wrap (done) ->
|
||||||
|
# yield fixEmailFormattedUsernames.run()
|
||||||
|
# user = yield User.findById(@user.id)
|
||||||
|
# expect(user.get('email')).toBe('an@email.com')
|
||||||
|
# expect(user.get('name')).toBe('a name')
|
||||||
|
# done()
|
||||||
|
#
|
||||||
|
# describe "when another user already has the email-formatted name as an email", ->
|
||||||
|
# beforeEach utils.wrap (done) ->
|
||||||
|
# @otherUser = new User({email: 'an@email.com'})
|
||||||
|
# yield @otherUser.save()
|
||||||
|
# done()
|
||||||
|
#
|
||||||
|
# it "slugifies the target user's username", utils.wrap (done) ->
|
||||||
|
# yield fixEmailFormattedUsernames.run()
|
||||||
|
# user = yield User.findById(@user.id)
|
||||||
|
# expect(user.get('email')).toBe('a name')
|
||||||
|
# expect(user.get('name')).toBe('anemailcom')
|
||||||
|
# done()
|
||||||
|
#
|
||||||
|
# describe "when another user already has the non-email-formatted email as a username", ->
|
||||||
|
# beforeEach utils.wrap (done) ->
|
||||||
|
# @otherUser = new User({name: 'a name'})
|
||||||
|
# yield @otherUser.save()
|
||||||
|
# done()
|
||||||
|
#
|
||||||
|
# it "slugifies the target user's username", utils.wrap (done) ->
|
||||||
|
# yield fixEmailFormattedUsernames.run()
|
||||||
|
# user = yield User.findById(@user.id)
|
||||||
|
# expect(user.get('email')).toBe('a name')
|
||||||
|
# expect(user.get('name')).toBe('anemailcom')
|
||||||
|
# done()
|
||||||
|
#
|
||||||
|
# describe "when the user has a different but well formatted email set", ->
|
||||||
|
# beforeEach utils.wrap (done) ->
|
||||||
|
# @user = new User({name: 'an@email.com', email: 'another@email.com'})
|
||||||
|
# @user.allowEmailNames = true
|
||||||
|
# yield @user.save()
|
||||||
|
# done()
|
||||||
|
#
|
||||||
|
# it "removes the target user's username", utils.wrap (done) ->
|
||||||
|
# yield fixEmailFormattedUsernames.run()
|
||||||
|
# user = yield User.findById(@user.id)
|
||||||
|
# expect(user.get('email')).toBe('another@email.com')
|
||||||
|
# expect(user.get('name')).toBeUndefined()
|
||||||
|
# done()
|
Loading…
Reference in a new issue