Merge branch 'master' into production

This commit is contained in:
Matt Lott 2016-08-05 13:28:36 -07:00
commit 8baa57748a
37 changed files with 4888 additions and 619 deletions

View file

@ -45,7 +45,7 @@ module.exports.applyErrorsToForm = (el, errors, warning=false) ->
for error in errors
if error.code is tv4.errorCodes.OBJECT_REQUIRED
prop = _.last(_.string.words(error.message)) # hack
message = 'Required field'
message = $.i18n.t('common.required_field')
else if error.dataPath
prop = error.dataPath[1..]

View file

@ -22,6 +22,9 @@ LOG = false
# * Sprite map generation
# * Connecting to Firebase
# LevelLoader depends on SuperModel retrying timed out requests, as these occasionally happen during play.
# If LevelLoader ever moves away from SuperModel, it will have to manage its own retries.
module.exports = class LevelLoader extends CocoClass
constructor: (options) ->

View file

@ -356,7 +356,7 @@
submit_patch: "Submit Patch"
submit_changes: "Submit Changes"
save_changes: "Save Changes"
required_field: "Required field"
required_field: "required" # {change}
general:
and: "and"
@ -885,7 +885,9 @@
evaluate_recommend: "Evaluate/Recommend"
approve_funds: "Approve Funds"
no_purchaser_role: "No role in purchase decisions"
organization_label: "Name of School/District"
district_label: "District"
district_na: "Enter N/A if not applicable"
organization_label: "School" # {change}
city: "City"
state: "State"
country: "Country"

2019
app/locale/hr.coffee Normal file

File diff suppressed because it is too large Load diff

View file

@ -39,8 +39,10 @@ module.exports =
'id': { nativeDescription: 'Bahasa Indonesia', englishDescription: 'Indonesian' }
'it': { nativeDescription: 'Italiano', englishDescription: 'Italian' }
'he': { nativeDescription: 'עברית', englishDescription: 'Hebrew' }
'hr': { nativeDescription: 'hrvatski jezik', englishDescription: 'Croatian' }
'hu': { nativeDescription: 'magyar', englishDescription: 'Hungarian' }
'lt': { nativeDescription: 'lietuvių kalba', englishDescription: 'Lithuanian' }
'mi': { nativeDescription: 'te reo Māori', englishDescription: 'Māori' }
'mk-MK': { nativeDescription: 'Македонски', englishDescription: 'Macedonian' }
'hi': { nativeDescription: 'मानक हिन्दी', englishDescription: 'Hindi' }
'ms': { nativeDescription: 'Bahasa Melayu', englishDescription: 'Bahasa Malaysia' }

2019
app/locale/mi.coffee Normal file

File diff suppressed because it is too large Load diff

View file

@ -132,7 +132,7 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
help_suff: "и мы свяжемся!"
modal:
# cancel: "Cancel"
cancel: "Отмена"
close: "Закрыть"
okay: "OK"
@ -234,7 +234,7 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
None: "значение, которое не указывает ни на один объект (ничто)"
share_progress_modal:
blurb: "Вы отлично продвигаетесь! Расскажите своим родителям, как много вы уже выучили с CodeCombat."
blurb: "Вы показываете отличные результаты! Расскажите своим родителям, как много вы уже выучили с CodeCombat."
email_invalid: "Email-адрес некорректен."
form_blurb: "Введите их email-адреса ниже, и мы покажем им!"
form_label: "Email-адрес"
@ -243,9 +243,9 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
login:
sign_up: "Создать аккаунт"
# email_or_username: "Email or username"
email_or_username: "Email или имя пользователя"
log_in: "Войти"
logging_in: "Вход..."
logging_in: "Входим..."
log_out: "Выйти"
forgot_password: "Забыли пароль?"
authenticate_gplus: "Аутентификация G+"
@ -256,72 +256,72 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
signup_switch: "Хотите создать аккаунт?"
signup:
# create_student_header: "Create Student Account"
# create_teacher_header: "Create Teacher Account"
# create_individual_header: "Create Individual Account"
# create_header: "Create Account"
email_announcements: "Получать оповещения по email" # {change}
creating: "Создание аккаунта..."
# create_account: "Create Account"
create_student_header: "Создать учетную запись Ученика"
create_teacher_header: "Создать учетную запись Учителя"
create_individual_header: "Создать личную учетную запись"
create_header: "Создать учетную запись"
email_announcements: "Получать оповещения по email о новых уровнях и возможностях на CodeCombat"
creating: "Создаем учетную запись..."
create_account: "Создать учетную запись"
sign_up: "Регистрация"
log_in: "вход с паролем"
required: "Войдите для того, чтобы продолжить."
login_switch: "Уже есть аккаунт?"
school_name: "Название школы and город"
login_switch: "Уже есть учетная запись?"
school_name: "Название школы и город"
optional: "не обязательно"
school_name_placeholder: "Школа № 2, город Электросталь, Московская область"
# connect_with: "Connect with:"
connect_with: "Зарегистрироваться с помощью:"
connected_gplus_header: "Вы успешно авторизовались через Google+!"
connected_gplus_p: "Теперь можно войти используя аккаунт Google+."
gplus_exists: "У вас уже имеется аккаунт связанный с Google+!"
connected_facebook_header: "Вы успешно авторизовались через Facebook!"
connected_facebook_p: "Теперь можно войти используя аккаунт Facebook."
facebook_exists: "У вас уже имеется аккаунт связанный сh Facebook!"
facebook_exists: "У вас уже имеется аккаунт связанный с Facebook!"
hey_students: "Студенты, введите код от класса вашего учителя."
# birthday: "Birthday"
# 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."
# checking: "Checking..."
# account_exists: "This email is already in use:" # {change}
# sign_in: "Sign in"
# email_good: "Email looks good!"
# name_taken: "Username already taken! Try {{suggestedName}}?"
# name_available: "Username available!"
# name_is_email: "Username may not be an email"
# choose_type: "Choose your account type:"
# teacher_type_1: "Teach programming using CodeCombat!"
# teacher_type_2: "Set up your class"
# teacher_type_3: "Access Course Guides"
# teacher_type_4: "View student progress"
# signup_as_teacher: "Sign up as a Teacher"
# student_type_1: "Learn to program while playing an engaging game!"
# student_type_2: "Play with your class"
# student_type_3: "Compete in arenas"
# student_type_4: "Choose your hero!"
# student_type_5: "Have your Class Code ready!"
# signup_as_student: "Sign up as a Student"
# individuals_or_parents: "Individuals & Parents"
# 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"
# enter_class_code: "Enter your Class Code"
# enter_birthdate: "Enter your birthdate:"
# ask_teacher_1: "Ask your teacher for your Class Code."
# ask_teacher_2: "Not part of a class? Create an "
# ask_teacher_3: "Individual Account"
# ask_teacher_4: " instead."
# about_to_join: "You're about to join:"
# enter_parent_email: "Enter your parents email address:"
# parent_email_error: "Something went wrong when trying to send the email. Check the email address and try again."
# parent_email_sent: "Weve sent an email with further instructions on how to create an account. Ask your parent to check their inbox."
# account_created: "Account Created!"
# 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!"
# write_this_down: "Write this down:"
# start_playing: "Start Playing!"
# sso_connected: "Successfully connected with:"
birthday: "День рождения"
parent_email_blurb: "Мы знаем что вы в нетерпении начать учиться программировать — мы тоже! Ваши родители получат письмо с дальнейшими инструкциями, как создать учетную запись. Пишите нам на email {{email_link}} если есть любые вопросы."
classroom_not_found: "Нет класса с таким кодом. Проверьте написание или попросите учителя помочь."
checking: "Проверяем..."
account_exists: "Этот email-адрес уже используется:" # {change}
sign_in: "Зарегистрироваться"
email_good: "С email-адресом все в порядке!"
name_taken: "Имя пользователя уже используется! Может {{suggestedName}}?"
name_available: "Имя пользователя доступно!"
name_is_email: "Имя пользователя не должно быть email-адресом"
choose_type: "Выбирите тип учетной записи:"
teacher_type_1: "Обучайте с помощью CodeCombat!"
teacher_type_2: "Настраивайте ваш класс"
teacher_type_3: "Получите доступ к учебным материалам "
teacher_type_4: "Следите за прогрессом учеников"
signup_as_teacher: "Зарегистрироваться как Учитель"
student_type_1: "Учитесь программировать, пока играете в захватывающую игру!"
student_type_2: "Играйте вместе с классом"
student_type_3: "Соревнуйтесь на аренах"
student_type_4: "Выбирайте своего героя!"
student_type_5: "Нужен актуальный код для класса!"
signup_as_student: "Зарегистрироваться как Ученик"
individuals_or_parents: "Индивидуальный и Родители"
individual_type: "Для игроков, которые учатся без класса. Родители должны создать учетную запись."
signup_as_individual: "Зарегистрироваться как индивидуальный игрок"
enter_class_code: "Введите ваш код для класса"
enter_birthdate: "Введите вашу дату рождения:"
ask_teacher_1: "Спросите вашего учителя код для класса."
ask_teacher_2: "Не относишься к учебному классу? Создай "
ask_teacher_3: "Личную учетную запись"
ask_teacher_4: " вместо этого."
about_to_join: "Вы присоединяетесь к:"
enter_parent_email: "Введите email-адрес одного из родителей:"
parent_email_error: "Что-то пошло не так, когда мы отправляли письмо. Проверьте email-адрес и повторите."
parent_email_sent: "Мы послали электронное письмо с дальнейшими инструкциями, как создать учетную запись. Попроси родителей проверить их входящие письма."
account_created: "Учетная запись создана!"
confirm_student_blurb: "Запишите ваши данные, чтобы не забыть их. Ваш учитель, если что сможет помочь сбросить пароль в любое время."
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: "Запишите это:"
start_playing: "Начать играть!"
sso_connected: "Успешно подключились с помощью:"
recover:
recover_account_title: "Восстановить аккаунт"
recover_account_title: "Восстановить учетную запись"
send_password: "Отправить пароль для восстановления"
recovery_sent: "Письмо с паролем отправлено."
@ -340,8 +340,8 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
saving: "Сохранение..."
sending: "Отправка..."
send: "Отправить"
# sent: "Sent"
# type: "Type"
sent: "Отправлено"
type: "Тип"
cancel: "Отмена"
save: "Сохранить"
publish: "Опубликовать"
@ -411,8 +411,8 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
wizard: "Волшебник"
first_name: "Имя"
last_name: "Фамилия"
# last_initial: "Last Initial"
username: "Имя юзера"
last_initial: "Инициалы фамилии"
username: "Имя пользователя"
units:
second: "секунда"
@ -431,13 +431,13 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
years: "лет"
play_level:
level_complete: "Уровень завершен"
completed_level: "Завершённый уровень:"
level_complete: "Уровень пройден"
completed_level: "Пройденный уровень:"
course: "Курс:"
done: "Готово"
next_level: "Следующий уровень"
next_game: "Следующая игра"
# programming_language: "Programming language"
programming_language: "Язык программирования"
show_menu: "Показать меню игры"
home: "На главную" # Not used any more, will be removed soon.
level: "Уровень" # Like "Level: Dungeons of Kithgard"
@ -458,12 +458,12 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
reload_confirm: "Перезагрузить всё"
victory: "Победа"
victory_title_prefix: "Уровень "
victory_title_suffix: " завершён"
victory_title_suffix: " пройден"
victory_sign_up: "Зарегистрируйтесь, чтобы сохранить прогресс"
victory_sign_up_poke: "Хотите сохранить ваш код? Создайте бесплатный аккаунт!"
victory_sign_up_poke: "Хотите сохранить ваш код? Создайте бесплатную учетную запись!"
victory_rate_the_level: "Оцените уровень:" # {change}
victory_return_to_ladder: "Вернуться к ладдеру"
victory_saving_progress: "Процесс сохранения"
victory_return_to_ladder: "Вернуться к таблице"
victory_saving_progress: "Сохранить прогресс"
victory_go_home: "На главную"
victory_review: "Расскажите нам больше!"
victory_review_placeholder: "Как вам уровень?"
@ -474,13 +474,13 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
victory_new_item: "Новый предмет"
victory_viking_code_school: "Ого, это было тяжелый уровень! Если вы еще не разработчик программ, вам стоит им стать. Вы только что ускорири принятие в Школу Викингов, где вы сможете поднять свои навыки на новый уровень и стать профессиональным веб-разработчиком за 14 недель."
victory_become_a_viking: "Станьте Викингом"
victory_no_progress_for_teachers: "Прогресс не сохраняется для учителей. Но, вы можете для себя добавить аккаунт студента в свою классную комнату."
victory_no_progress_for_teachers: "Прогресс не сохраняется для учителей. Но, вы можете для себя добавить учетную запись ученика в свою классную комнату."
guide_title: "Руководство"
tome_cast_button_run: "Запустить"
tome_cast_button_running: "В процессе"
tome_cast_button_ran: "Запущен"
tome_submit_button: "Завершить"
tome_reload_method: "Загрузить оригинальный код для этого метода" # {change}
tome_reload_method: "Загрузить оригинальный код и начать уровень заново" # {change}
tome_available_spells: "Доступные заклинания"
tome_your_skills: "Ваши навыки"
tome_current_method: "Текущий метод"
@ -491,13 +491,13 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
keyboard_shortcuts: "Горячие клавиши"
loading_ready: "Готово!"
loading_start: "Начать уровень"
problem_alert_title: "Исправьте ваш Код"
problem_alert_title: "Исправьте код"
time_current: "Текущее:"
time_total: "Максимальное:"
time_goto: "Перейти на:"
non_user_code_problem_title: "Невозможно загрузить уровень"
infinite_loop_title: "Обнаружен бесконечный цикл"
infinite_loop_description: "Код сотворения мира не завершил выполнение. Это могло случиться из-за реально медленного кода или наличия бесконечного цикла. Или там может быть баг. Вы можете попытаться запустить этот код еще раз или сбросить код в состояние по умолчанию. Если проблема не будет решена, дайте нам знать."
infinite_loop_description: "Код сотворения мира не завершил выполнение. Это могло случиться из-за очень медленного кода или наличия бесконечного цикла. Или там может быть баг. Вы можете попытаться запустить этот код еще раз или сбросить код в состояние по умолчанию. Если проблема не будет решена, дайте нам знать."
check_dev_console: "Вы так же можете открыть консоль разработчика, чтобы увидеть, что может идти не так."
check_dev_console_link: "(инструкции)"
infinite_loop_try_again: "Попробовать снова"
@ -542,32 +542,32 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
tip_optimization_operator: "В каждом языке есть оператор оптимизации. В большинстве языков это оператор //"
tip_lines_of_code: "Измерение прогресса программирования в строках кода - это как измерять прогресс построения самолета по его весу. — Bill Gates"
tip_source_code: "Я хочу изменить мир, но они вряд ли дадут мне исходники."
tip_javascript_java: "Java к JavaScript относится так же, как кол относится к колготкам. - Chris Heilmann (перефраз.)"
tip_move_forward: "Что бы вы ни делали, вы должны двигаться вперед. - Martin Luther King Jr"
tip_javascript_java: "Java к JavaScript относится так же, как кол относится к колготкам. (перефраз.) - Крис Хейльман"
tip_move_forward: "Что бы вы ни делали, вы должны двигаться вперед. - Мартин Лютер Кинг Мл."
tip_google: "У вас проблема, которую вы не можете решить? Гуглите!"
tip_adding_evil: "Добавим щепотку зла."
tip_hate_computers: "Есть одна вещь в людях, которые думают, что они ненавидят компьютеры. Что они на самом деле ненавидят, так это плохих программистов. - Larry Niven"
tip_open_source_contribute: "Вы можете помочь сделать CodeCombat лучше!"
tip_hate_computers: "Есть одна вещь в людях, которые думают, что они ненавидят компьютеры. Что они на самом деле ненавидят, так это плохих программистов. - Ларри Нивен"
tip_open_source_contribute: "Ты можешь помочь сделать CodeCombat лучше!"
tip_recurse: "Итерация свойственна человеку, рекурсия божественна. - L. Peter Deutsch"
tip_free_your_mind: "Отвлекись от всего, Нео. Страх, неверие, сомнения отбрось — очисти свой разум. - Morpheus"
tip_strong_opponents: "Даже сильнейший противник имеет слабость. - Itachi Uchiha"
tip_free_your_mind: "Отвлекись от всего, Нео. Страх, неверие, сомнения отбрось — очисти свой разум. - Морфей"
tip_strong_opponents: "Даже сильнейший противник имеет слабость. - Итачи Учиха"
tip_paper_and_pen: "Прежде чем начать программировать, вы всегда можете попробовать с листом бумаги и ручкой."
tip_solve_then_write: "Сперва реши задачу, затем пиши код. - Джон Джонсон"
tip_compiler_ignores_comments: "Порой мне кажется, что компилятор игнорирует мои комментарии."
tip_understand_recursion: "Единственный способ понять рекурсию, это понять рекурсию."
tip_life_and_polymorphism: "Открытый исходный код это как полоностью полиморфная гетерогенная структура: Все типы приветствуются."
tip_mistakes_proof_of_trying: "Ошибка в коде подтвердила твои старания."
# tip_adding_orgres: "Rounding up ogres."
# tip_sharpening_swords: "Sharpening the swords."
tip_adding_orgres: "Собираем огров."
tip_sharpening_swords: "Точим мечи."
# tip_ratatouille: "You must not let anyone define your limits because of where you come from. Your only limit is your soul. - Gusteau, Ratatouille"
# tip_nemo: "When life gets you down, want to know what you've gotta do? Just keep swimming, just keep swimming. - Dory, Finding Nemo"
# tip_internet_weather: "Just move to the internet, it's great here. We get to live inside where the weather is always awesome. - John Green"
# tip_nerds: "Nerds are allowed to love stuff, like jump-up-and-down-in-the-chair-can't-control-yourself love it. - John Green"
# tip_self_taught: "I taught myself 90% of what I've learned. And that's normal! - Hank Green"
# tip_luna_lovegood: "Don't worry, you're just as sane as I am. - Luna Lovegood"
# tip_good_idea: "The best way to have a good idea is to have a lot of ideas. - Linus Pauling"
tip_self_taught: "Я самостоятельно научился 90% из того чему учился. И это нормально! - Хэнк Грин"
tip_luna_lovegood: "Не переживай, ты также в своем уме, как и я. - Луна Лавгуд"
tip_good_idea: "Лучший способ найти хорошую идею - иметь множество идей. - Линус Полинг"
# tip_programming_not_about_computers: "Computer Science is no more about computers than astronomy is about telescopes. - Edsger Dijkstra"
# tip_mulan: "Believe you can, then you will. - Mulan"
tip_mulan: "Верь, что можешь и получится. - Мулан"
game_menu:
inventory_tab: "Инвентарь"
@ -694,7 +694,7 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
javascript_blurb: "Язык для Сети."
coffeescript_blurb: "Улучшенный синтаксис JavaScript."
lua_blurb: "Скриптовый язык для игр."
# java_blurb: "(Subscriber Only) Android and enterprise."
java_blurb: "(только для подписчиков) Андроид и бизнес."
status: "Статус"
hero_type: "Тип"
weapons: "Оружие"
@ -705,7 +705,7 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
health: "Жизнь"
speed: "Скорость"
regeneration: "Регенерация"
range: "Зона" # As in "attack or visual range"
range: "Дальность" # As in "attack or visual range"
blocks: "Блокирует" # As in "this shield blocks this much damage"
backstab: "Со спины" # As in "this dagger does this much backstab damage"
skills: "Умения"
@ -775,32 +775,33 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
story_link: "История"
press_link: "Прессе"
mission_title: "Наша задача: сделать доступным программирование для каждого учащегося на земле."
# mission_description_1: "<strong>Programming is magic</strong>. It's the ability to create things from pure imagination. We started CodeCombat to give learners the feeling of wizardly power at their fingertips by using <strong>typed code</strong>."
# mission_description_2: "As it turns out, that enables them to learn faster too. WAY faster. It's like having a conversation instead of reading a manual. We want to bring that conversation to every school and to <strong>every student</strong>, because everyone should have the chance to learn the magic of programming."
mission_description_1: "<strong>Программирование - это магия</strong>.
Это способность создавать что-то с помощью воображения. Мы создавали CodeCombat, чтобы дать учащимся чувство силы волшебства на кончиках пальцев, когда они <strong>пишут код</strong>."
mission_description_2: "Как оказалось, это позволяет им учится быстрее. СИЛЬНО быстрее. Это как живой рассказ вместо чтения учебника. Мы хотим принести этот метод в каждую школу и <strong>каждому ученику</strong>, потому что все должны иметь шанс научится магии программирования."
team_title: "Присоединяйтесь к команде CodeCombat"
# team_values: "We value open and respectful dialog, where the best idea wins. Our decisions are grounded in customer research and our process is focused on delivering tangible results for them. Everyone is hands-on, from our CEO to our GitHub contributors, because we value growth and learning in our team."
nick_title: "Сооснователь" # {change}
team_values: "Мы ценим открытый и вежливый диалог, где побеждают лучшие идеи. Наши решения основаны на иследовании пожеланий клиентов и наш процесс направлен на то, чтобы приносить осязаемые результаты им. У нас все при деле, от CEO до контрибьютеров на GitHub, потому что мы ценим рост и обучение в нашей команде."
nick_title: "Сооснователь, CEO"
nick_blurb: "Гуру мотивации"
matt_title: "Сооснователь" # {change}
cat_title: "Главный ремесленник" # {change}
matt_title: "Сооснователь, CTO"
cat_title: "Гейм дизайнер"
cat_blurb: "Повелитель стихий"
scott_title: "Сооснователь" # {change}
scott_blurb: "Разумный"
# maka_title: "Customer Advocate"
# maka_blurb: "Storyteller"
# rob_title: "Software Engineer"
# rob_blurb: "Codes things and stuff"
# josh_c_title: "Game Designer"
# josh_c_blurb: "Designs games"
# robin_title: "UX Design & Research"
# robin_blurb: "Scaffolding"
josh_title: "Дизайнер игры"
scott_title: "Сооснователь, инженер программист" # {change}
scott_blurb: "Благоразумный"
maka_title: "Адвокат клиентов"
maka_blurb: "Рассказчик"
rob_title: "Инженер программист"
rob_blurb: "Программирует все"
josh_c_title: "Гейм дизайнер"
josh_c_blurb: "Делает игры"
robin_title: "UX дизайнер & Исследования"
# robin_blurb: "Scaffolding"
josh_title: "Гейм дизайнер"
josh_blurb: "Пол - это лава"
# phoenix_title: "Software Engineer"
# nolan_title: "Territory Manager"
# elliot_title: "Partnership Manager"
# elliot_blurb: "Mindreader"
# lisa_title: "Market Development Rep"
phoenix_title: "Инженер программист"
nolan_title: "Региональный менеджер"
elliot_title: "Менеджер партнерства"
elliot_blurb: "Читающий мысли"
lisa_title: "Представитель по развитию рынка"
retrostyle_title: "Иллюстрирование"
retrostyle_blurb: "RetroStyle Games"
jose_title: "Музыка"

View file

@ -298,6 +298,10 @@ class ModelResource extends Resource
@loadsAttempted = 0
load: ->
# TODO: Track progress on requests and don't retry if progress was made recently.
# Probably use _.debounce and attach event listeners to xhr objects.
# This logic is for handling failed responses for level loading.
timeToWait = 5000
tryLoad = =>
return if this.isLoaded

View file

@ -38,7 +38,7 @@ module.exports = class ThangType extends CocoModel
urlRoot: '/db/thang.type'
building: {}
editableByArtisans: true
@defaultActions: ['idle', 'die', 'move', 'attack']
@defaultActions: ['idle', 'die', 'move', 'attack', 'trick', 'cast']
initialize: ->
super()

View file

@ -9,7 +9,7 @@ _.extend CampaignSchema.properties, {
i18n: {type: 'object', title: 'i18n', format: 'i18n', props: ['name', 'fullName', 'description']}
fullName: { type: 'string', title: 'Full Name', description: 'Ex.: "Kithgard Dungeon"' }
description: { type: 'string', format: 'string', description: 'How long it takes and what players learn.' }
type: c.shortString(title: 'Type', description: 'What kind of campaign this is.', 'enum': ['hero', 'course'])
type: c.shortString(title: 'Type', description: 'What kind of campaign this is.', 'enum': ['hero', 'course','hidden'])
ambientSound: c.object {},
mp3: { type: 'string', format: 'sound-file' }

View file

@ -1,6 +1,6 @@
#components-documentation-view
background-color: #e4cf8c
height: 100%
height: calc(100% - 90px)
#toggle-all-component-code
margin: 10px

View file

@ -1,96 +0,0 @@
@import "app/styles/mixins"
@import "app/styles/bootstrap/variables"
#request-quote-view
#site-content-area
margin: 50px 0 100px
.row
margin: 20px 0
#conversion-warning
margin-top: 20px
.form-group
label
margin-bottom: 0
label.checkbox
font-weight: normal
.help-block
margin: -4px 0 2px
p
margin: 0 0 20px
.checkbox, .checkbox-inline
input
margin-top: 8px
#other-education-level-input
label
display: inline-block
display: inline-block
width: 200px
margin-left: 5px
#submit-request-btn
margin-left: 10px
// After submit (anonymous)
h5
margin-top: 50px
#social-network-signups
margin: 20px 0
button
margin-left: 10px
.text-h1
margin: 40px 0 30px
.algolia-autocomplete
width: 100%;
.aa-input
width: 100%
.aa-hint
color: #999
width: 100%
.aa-dropdown-menu
background-color: #fff
border: 1px solid #999
border-top: none
width: 100%
.aa-suggestion
cursor: pointer
padding: 5px 4px
border-top: 1px solid #ccc
.school
font-family: Open Sans
font-size: 14px
line-height: 20px
font-weight: bold
.district
font-family: Open Sans
font-size: 14px
line-height: 20px
span
white-space: nowrap
.aa-suggestion.aa-cursor
background-color: #B2D7FF
em
font-weight: bold
font-style: normal

View file

@ -0,0 +1,81 @@
#create-teacher-account-view, #convert-to-teacher-account-view, #request-quote-view
.algolia-autocomplete
width: 100%;
.aa-input
width: 100%
.aa-hint
color: #999
width: 100%
.aa-dropdown-menu
background-color: #fff
border: 1px solid #999
border-top: none
width: 100%
.aa-suggestion
cursor: pointer
padding: 5px 4px
border-top: 1px solid #ccc
.school
font-family: Open Sans
font-size: 14px
line-height: 20px
font-weight: bold
.district
font-family: Open Sans
font-size: 14px
line-height: 20px
span
white-space: nowrap
.aa-suggestion.aa-cursor
background-color: #B2D7FF
em
font-weight: bold
font-style: normal
// TODO: update form validation instead of overwriting these styles
.control-label
font-weight: bold
width: 100%
.error-help-block
margin-top: inherit
margin-bottom: 0px
float: right
font-size: 13px
font-style: italic
font-weight: normal
.text-muted
float: right
font-size: 13px
font-style: italic
font-weight: normal
.nullify-form-control
display: inherit
width: inherit
height: inherit
padding: inherit
font-size: inherit
line-height: inherit
color: inherit
vertical-align: inherit
background-color: inherit
background-image: inherit
border: inherit
border-radius: inherit
-webkit-box-shadow: inherit
box-shadow: inherit
-webkit-transition: inherit
transition: inherit

View file

@ -16,6 +16,8 @@ block content
th Course
each course in view.courses.models
- var campaign = view.campaigns.get(course.get('campaignID'));
if !campaign
- continue;
- var levels = campaign.getLevels().models;
- levelsTotal += levels.length;
tr
@ -26,6 +28,8 @@ block content
td All
each course in view.courses.models
- var campaign = view.campaigns.get(course.get('campaignID'));
if !campaign
- continue;
- var levels = campaign.getLevels().models;
- levelCounts = levels.length;
strong #{course.get('name')}

View file

@ -30,8 +30,8 @@ block content
thead
tr
th Created
th NCES District
th School Name
th School District
th.number NCES District Schools
th.number NCES District Students
th.number NCES School Students
@ -44,8 +44,8 @@ block content
- continue;
tr
td.created= trialRequest.get('created').substring(0, 10)
td= trialRequest.get('properties').nces_district || ''
td= trialRequest.get('properties').organization || ''
td= trialRequest.get('properties').nces_name || trialRequest.get('properties').organization || ''
td= trialRequest.get('properties').nces_district || trialRequest.get('properties').district || ''
td= trialRequest.get('properties').nces_district_schools || ''
td= trialRequest.get('properties').nces_district_students || ''
td= trialRequest.get('properties').nces_students || ''

View file

@ -144,12 +144,12 @@ block outer_content
div.tab-pane#related-achievements-view
div.tab-pane#editor-level-documentation
ul.nav.nav-pills.nav-justified
li
a(href="#components-documentation-view", data-toggle="pill", data-i18n="resources.components") Components
li
a(href="#systems-documentation-view", data-toggle="pill", data-i18n="resources.systems") Systems
div.tab-content
ul.nav.nav-pills.nav-justified
li
a(href="#components-documentation-view", data-toggle="pill", data-i18n="resources.components") Components
li
a(href="#systems-documentation-view", data-toggle="pill", data-i18n="resources.systems") Systems
div.tab-pane#components-documentation-view
div.tab-pane#systems-documentation-view

View file

@ -1,23 +1,25 @@
button.btn.btn-primary#new-achievement-button(disabled=(me.isAdmin() === true || me.isArtisan() === true) ? undefined : "true" data-i18n="editor.new_achievement_title") Create a New Achievement
.nano.editor-nano-container
.nano-content
button.btn.btn-primary#new-achievement-button(disabled=(me.isAdmin() === true || me.isArtisan() === true) ? undefined : "true" data-i18n="editor.new_achievement_title") Create a New Achievement
if !view.achievements.models.length
.panel
.panel-body
p(data-i18n="editor.no_achievements") No achievements added for this level yet.
else
table.table.table-hover
thead
tr
th
th(data-i18n="general.name") Name
th(data-i18n="general.description") Description
th XP
tbody
each achievement in view.achievements.models
tr
td(style="width: 20px")
img.achievement-icon-small(src=achievement.getImageURL() alt="#{achievement.get('name') icon")
td
a(href="/editor/achievement/#{achievement.get('slug')}")= achievement.get('name', true)
td= achievement.get('description', true)
td= achievement.get('worth', true)
if !view.achievements.models.length
.panel
.panel-body
p(data-i18n="editor.no_achievements") No achievements added for this level yet.
else
table.table.table-hover
thead
tr
th
th(data-i18n="general.name") Name
th(data-i18n="general.description") Description
th XP
tbody
each achievement in view.achievements.models
tr
td(style="width: 20px")
img.achievement-icon-small(src=achievement.getImageURL() alt="#{achievement.get('name') icon")
td
a(href="/editor/achievement/#{achievement.get('slug')}")= achievement.get('name', true)
td= achievement.get('description', true)
td= achievement.get('worth', true)

View file

@ -1,42 +1,44 @@
mixin task-row(cid)
- var task = view.getTaskByCID(cid)
- var taskName = task.get('name');
- var isComplete = task.get('complete')
tr.task-row(data-task-cid=cid)
td.task-check
div.checkbox
input(type='checkbox', checked=(isComplete || false)).task-input
if task.get('curEdit') == true
td.edit-cell
td.task-name
input(type="input", value=taskName)#cur-edit
else
td.edit-cell
span.glyphicon.glyphicon-edit.start-edit
td.task-name
- var result = view.getTaskURL(taskName)
if result !== null
// https://github.com/codecombat/codecombat/wiki/Tasks-Tab#<slug goes here>
a(href='https://github.com/codecombat/codecombat/wiki/Tasks-Tab#' + result, target='blank')= taskName
.nano.editor-nano-container
.nano-content
mixin task-row(cid)
- var task = view.getTaskByCID(cid)
- var taskName = task.get('name');
- var isComplete = task.get('complete')
tr.task-row(data-task-cid=cid)
td.task-check
div.checkbox
input(type='checkbox', checked=(isComplete || false)).task-input
if task.get('curEdit') == true
td.edit-cell
td.task-name
input(type="input", value=taskName)#cur-edit
else
span= taskName
td.edit-cell
span.glyphicon.glyphicon-edit.start-edit
td.task-name
- var result = view.getTaskURL(taskName)
if result !== null
// https://github.com/codecombat/codecombat/wiki/Tasks-Tab#<slug goes here>
a(href='https://github.com/codecombat/codecombat/wiki/Tasks-Tab#' + result, target='blank')= taskName
else
span= taskName
block
table.table.table-striped.table-hover
tr
th.task-check Complete
th.edit-cell Edit
th Incomplete Tasks
for task in (view.taskArray() || [])
if task.get('revert').complete !== true
+task-row(task.cid)
tr
th.task-check
th.edit-cell
th Completed Tasks
for task in (view.taskArray() || [])
if task.get('revert').complete === true
+task-row(task.cid)
button#create-task.btn.btn-primary Add Task
if view.missingDefaults().length !== 0
button#add-default-tasks.btn.btn-default Add Default Tasks
block
table.table.table-striped.table-hover
tr
th.task-check Complete
th.edit-cell Edit
th Incomplete Tasks
for task in (view.taskArray() || [])
if task.get('revert').complete !== true
+task-row(task.cid)
tr
th.task-check
th.edit-cell
th Completed Tasks
for task in (view.taskArray() || [])
if task.get('revert').complete === true
+task-row(task.cid)
button#create-task.btn.btn-primary Add Task
if view.missingDefaults().length !== 0
button#add-default-tasks.btn.btn-default Add Default Tasks

View file

@ -28,40 +28,36 @@ block content
.row.m-y-2
.col-md-offset-2.col-md-4.col-sm-6
.form-group
label.control-label(data-i18n="general.username")
span.control-label(data-i18n="general.username")
input.form-control(disabled=true value=me.get('name'))
.col-md-4.col-sm-6
#email-form-group.form-group
label.control-label(data-i18n="general.email")
span.control-label(data-i18n="general.email")
input.form-control(name='email' disabled=true value=me.get('email'))
.row.m-y-2
.col-md-offset-2.col-md-4.col-sm-6
.form-group
label.control-label(data-i18n="general.first_name")
span.control-label(data-i18n="general.first_name")
input.form-control(name="firstName" value=me.get('firstName') || '')
.col-md-4.col-sm-6
.form-group
label.control-label(data-i18n="general.last_name")
span.control-label(data-i18n="general.last_name")
input.form-control(name="lastName" value=me.get('lastName') || '')
.row.m-y-2
.col-md-offset-2.col-md-4.col-sm-6
.form-group
label.control-label
span.control-label
span(data-i18n="teachers_quote.phone_number")
span.spl.text-muted(data-i18n="signup.optional")
.help-block.small
em.text-info(data-i18n="teachers_quote.phone_number_help")
input.form-control(name="phoneNumber")
input.form-control(name="phoneNumber", data-i18n="[placeholder]teachers_quote.phone_number_help")
.col-md-4.col-sm-6
.form-group
label.control-label(data-i18n="teachers_quote.primary_role_label")
.help-block.small
em.text-info(data-i18n="teachers_quote.role_help")
span.control-label(data-i18n="teachers_quote.primary_role_label")
select.form-control(name="role")
option(data-i18n="teachers_quote.role_default", , value='')
option(data-i18n="courses.teacher", value="Teacher")
@ -75,30 +71,40 @@ block content
.row.m-y-2
.col-md-offset-2.col-md-4.col-sm-6
.form-group
label.control-label(data-i18n="teachers_quote.organization_label")
span.control-label
span(data-i18n="teachers_quote.organization_label")
span.spl.text-muted(data-i18n="signup.optional")
input.form-control#organization-control(name="organization")
.col-md-4.col-sm-6
.form-group
label.control-label(data-i18n="teachers_quote.city")
input.form-control(name="city")
//- TODO: algolia and form errors both change form-control
//- TODO: District not red on validation error
span.control-label.form-control.nullify-form-control(data-i18n="teachers_quote.district_label")
input.form-control#district-control(name="district", data-i18n="[placeholder]teachers_quote.district_na")
.row.m-y-2
.col-md-offset-2.col-md-4.col-sm-6
.form-group
label.control-label(data-i18n="teachers_quote.state")
input.form-control(name="state")
span.control-label(data-i18n="teachers_quote.city")
input.form-control(name="city")
.col-md-4.col-sm-6
.form-group
label.control-labellabel.control-label(data-i18n="teachers_quote.country")
span.control-label(data-i18n="teachers_quote.state")
input.form-control(name="state")
.row.m-y-2
.col-md-offset-2.col-md-4.col-sm-6
.form-group
span.control-labelspan.control-label(data-i18n="teachers_quote.country")
input.form-control(name="country")
#form-students-info
.row.m-y-2
.col-md-offset-2.col-md-4
.form-group
label.control-label(data-i18n="courses.number_programming_students")
span.control-label(data-i18n="courses.number_programming_students")
.help-block.small
em.text-info(data-i18n="teachers_quote.num_students_help")
select.form-control(name="numStudents")
@ -113,7 +119,7 @@ block content
.col-md-4.col-sm-6
.form-group
label.control-label
span.control-label
span(data-i18n="courses.number_total_students")
span.spl.text-muted(data-i18n="signup.optional")
select.form-control(name="numStudentsTotal")
@ -126,11 +132,10 @@ block content
.form-group
.row.m-y-2
.col-md-offset-2.col-md-10
label.control-label(data-i18n="teachers_quote.education_level_label")
.col-md-offset-2.col-md-4
span.control-label(data-i18n="teachers_quote.education_level_label")
.help-block.small
em.text-info(data-i18n="teachers_quote.education_level_help")
.col-md-offset-2.col-md-5
.checkbox
label
input(type="checkbox" name="educationLevel" value="Elementary")
@ -156,7 +161,7 @@ block content
#anything-else-row.row.m-y-2
.col-md-offset-2.col-md-8
label.control-label
span.control-label
span(data-i18n="teachers_quote.anything_else")
span.spl.text-muted(data-i18n="signup.optional")
textarea.form-control(rows=8, name="notes")

View file

@ -34,49 +34,47 @@ block content
.row.m-y-2
.col-md-offset-2.col-md-4.col-sm-6
.form-group
label.control-label(data-i18n="general.username")
span.control-label(data-i18n="general.username")
input.form-control(name="name")
.col-md-4.col-sm-6
#email-form-group.form-group
label.control-label(data-i18n="general.email")
span.control-label(data-i18n="general.email")
input.form-control(name="email")
.row.m-y-2
.col-md-offset-2.col-md-4.col-sm-6
.form-group
label.control-label(data-i18n="general.first_name")
span.control-label(data-i18n="general.first_name")
input.form-control(name="firstName")
.col-md-4.col-sm-6
.form-group
label.control-label(data-i18n="general.last_name")
span.control-label(data-i18n="general.last_name")
input.form-control(name="lastName")
.row.m-y-2
.col-md-offset-2.col-md-4.col-sm-6
.form-group
label.control-label(data-i18n="general.password")
span.control-label(data-i18n="general.password")
input.form-control(name="password1", type="password")
.col-md-4.col-sm-6
.form-group
label.control-label(data-i18n="general.confirm_password")
span.control-label(data-i18n="general.confirm_password")
input.form-control(name="password2", type="password")
.row.m-y-2
.col-md-offset-2.col-md-4.col-sm-6
.form-group
label.control-label
span.control-label
span(data-i18n="teachers_quote.phone_number")
span.spl.text-muted(data-i18n="signup.optional")
.help-block.small
em.text-info(data-i18n="teachers_quote.phone_number_help")
input.form-control(name="phoneNumber")
input.form-control(name="phoneNumber", data-i18n="[placeholder]teachers_quote.phone_number_help")
.col-md-4.col-sm-6
.form-group
label.control-label(data-i18n="teachers_quote.primary_role_label")
span.control-label(data-i18n="teachers_quote.primary_role_label")
select.form-control(name="role")
option(data-i18n="teachers_quote.role_default", , value='')
option(data-i18n="courses.teacher", value="Teacher")
@ -90,30 +88,40 @@ block content
.row.m-y-2
.col-md-offset-2.col-md-4.col-sm-6
.form-group
label.control-label(data-i18n="teachers_quote.organization_label")
span.control-label
span(data-i18n="teachers_quote.organization_label")
span.spl.text-muted(data-i18n="signup.optional")
input.form-control#organization-control(name="organization")
.col-md-4.col-sm-6
.form-group
label.control-label(data-i18n="teachers_quote.city")
input.form-control(name="city")
//- TODO: algolia and form errors both change form-control
//- TODO: District not red on validation error
span.control-label.form-control.nullify-form-control(data-i18n="teachers_quote.district_label")
input.form-control#district-control(name="district", data-i18n="[placeholder]teachers_quote.district_na")
.row.m-y-2
.col-md-offset-2.col-md-4.col-sm-6
.form-group
label.control-label(data-i18n="teachers_quote.state")
input.form-control(name="state")
span.control-label(data-i18n="teachers_quote.city")
input.form-control(name="city")
.col-md-4.col-sm-6
.form-group
label.control-labellabel.control-label(data-i18n="teachers_quote.country")
span.control-label(data-i18n="teachers_quote.state")
input.form-control(name="state")
.row.m-y-2
.col-md-offset-2.col-md-4.col-sm-6
.form-group
span.control-label(data-i18n="teachers_quote.country")
input.form-control(name="country")
#form-students-info
.row.m-y-2
.col-md-offset-2.col-md-4
.form-group
label.control-label(data-i18n="courses.number_programming_students")
span.control-label(data-i18n="courses.number_programming_students")
.help-block.small
em.text-info(data-i18n="teachers_quote.num_students_help")
select.form-control(name="numStudents")
@ -128,7 +136,7 @@ block content
.col-md-4.col-sm-6
.form-group
label.control-label
span.control-label
span(data-i18n="courses.number_total_students")
span.spl.text-muted(data-i18n="signup.optional")
select.form-control(name="numStudentsTotal")
@ -140,14 +148,11 @@ block content
option 10,000+
.form-group
.row.m-y-2
.col-md-offset-2.col-md-10
label.control-label(data-i18n="teachers_quote.education_level_label")
.help-block.small
em.text-info(data-i18n="teachers_quote.education_level_help")
.col-md-offset-2.col-md-5
.col-md-offset-2.col-md-4
span.control-label(data-i18n="teachers_quote.education_level_label")
.help-block.small
em.text-info(data-i18n="teachers_quote.education_level_help")
.checkbox
label
input(type="checkbox" name="educationLevel" value="Elementary")
@ -173,7 +178,7 @@ block content
#anything-else-row.row.m-y-2
.col-md-offset-2.col-md-8
label.control-label
span.control-label
span(data-i18n="teachers_quote.anything_else")
span.spl.text-muted(data-i18n="signup.optional")

View file

@ -13,7 +13,9 @@ if completed
.small-details.nowrap
span.spr(data-i18n='teacher.completed')
| Completed
span= new Date(session.get('dateFirstCompleted')).toLocaleString()
- var dateCompleted = session.get('dateFirstCompleted') || session.get('created') || session.get('changed');
if dateCompleted
span= new Date(dateCompleted).toLocaleString()
+timePlayed
//- .small-details
//- i(data-i18n='teacher.click_to_view_solution')

View file

@ -36,7 +36,7 @@ block content
.row
.col-md-offset-2.col-md-4.col-sm-6
.form-group
label.control-label
span.control-label
span(data-i18n="general.username")
span.spl.text-muted(data-i18n="signup.optional")
- var name = me.get('name') || '';
@ -44,20 +44,20 @@ block content
.col-md-4.col-sm-6
.form-group
label.control-label(data-i18n="share_progress_modal.form_label")
span.control-label(data-i18n="share_progress_modal.form_label")
- var email = me.get('email') || '';
input.form-control(name="email" value=email, disabled=!!email)
.row
.col-md-offset-2.col-md-4.col-sm-6
.form-group
label.control-label(data-i18n="general.first_name")
span.control-label(data-i18n="general.first_name")
- var firstName = me.get('firstName') || '';
input.form-control(name="firstName" value=firstName, disabled=!!firstName)
.col-md-4.col-sm-6
.form-group
label.control-label(data-i18n="general.last_name")
span.control-label(data-i18n="general.last_name")
- var lastName = me.get('lastName') || '';
input.form-control(name="lastName" value=lastName, disabled=!!lastName)
@ -65,33 +65,30 @@ block content
.row
.col-md-offset-2.col-md-4.col-sm-6
.form-group
label.control-label
span(data-i18n="teachers_quote.phone_number")
.help-block.small
em.text-info(data-i18n="teachers_quote.phone_number_help")
input.form-control(name="phoneNumber")
span.control-label(data-i18n="teachers_quote.phone_number")
input.form-control(name="phoneNumber", data-i18n="[placeholder]teachers_quote.phone_number_help")
if me.isAnonymous()
.row
.col-md-offset-2.col-md-4.col-sm-6
#email-form-group.form-group
label.control-label(data-i18n="general.email")
span.control-label(data-i18n="general.email")
- var email = me.get('email') || '';
input.form-control(name="email" type="email", value=email, disabled=!!email)
.col-md-4.col-sm-6
.form-group
label.control-label
span.control-label
span(data-i18n="teachers_quote.phone_number")
.help-block.small
em.text-info(data-i18n="teachers_quote.phone_number_help")
input.form-control(name="phoneNumber")
input.form-control(name="phoneNumber")
.row
.col-md-offset-2.col-md-4.col-sm-6
.form-group
label.control-label(data-i18n="teachers_quote.primary_role_label")
span.control-label(data-i18n="teachers_quote.primary_role_label")
select.form-control(name="role")
option(data-i18n="teachers_quote.primary_role_default", , value='')
option(data-i18n="courses.teacher", value="Teacher")
@ -100,45 +97,55 @@ block content
option(data-i18n="teachers_quote.principal", value="Principal")
option(data-i18n="teachers_quote.superintendent", value="Superintendent")
option(data-i18n="teachers_quote.parent", value="Parent")
.col-md-4.col-sm-6
.col-md-4.col-sm-6
.form-group
label.control-label(data-i18n="teachers_quote.purchaser_role_label")
span.control-label(data-i18n="teachers_quote.purchaser_role_label")
select.form-control(name="purchaserRole")
option(data-i18n="teachers_quote.purchaser_role_default", , value='')
option(data-i18n="teachers_quote.influence_advocate", value="Influence/Advocate")
option(data-i18n="teachers_quote.evaluate_recommend", value="Evaluate/Recommend")
option(data-i18n="teachers_quote.approve_funds", value="Approve Funds")
option(data-i18n="teachers_quote.no_purchaser_role", value="No role in purchase decisions")
#form-school-info
.row
.row.m-y-2
.col-md-offset-2.col-md-4.col-sm-6
.form-group
label.control-label(data-i18n="teachers_quote.organization_label")
span.control-label
span(data-i18n="teachers_quote.organization_label")
span.spl.text-muted(data-i18n="signup.optional")
input.form-control#organization-control(name="organization")
.col-md-4.col-sm-6
.form-group
label.control-label(data-i18n="teachers_quote.city")
input.form-control(name="city")
//- TODO: algolia and form errors both change form-control
//- TODO: District not red on validation error
span.control-label.form-control.nullify-form-control(data-i18n="teachers_quote.district_label")
input.form-control#district-control(name="district", data-i18n="[placeholder]teachers_quote.district_na")
.row
.row.m-y-2
.col-md-offset-2.col-md-4.col-sm-6
.form-group
label.control-label(data-i18n="teachers_quote.state")
input.form-control(name="state")
span.control-label(data-i18n="teachers_quote.city")
input.form-control(name="city")
.col-md-4.col-sm-6
.form-group
label.control-labellabel.control-label(data-i18n="teachers_quote.country")
span.control-label(data-i18n="teachers_quote.state")
input.form-control(name="state")
.row.m-y-2
.col-md-offset-2.col-md-4.col-sm-6
.form-group
span.control-labelspan.control-label(data-i18n="teachers_quote.country")
input.form-control(name="country")
#form-students-info
.row
.col-md-offset-2.col-md-4
.form-group
label.control-label(data-i18n="courses.number_programming_students")
span.control-label(data-i18n="courses.number_programming_students")
.help-block.small
em.text-info(data-i18n="teachers_quote.num_students_help")
select.form-control(name="numStudents")
@ -150,10 +157,10 @@ block content
option 201-500
option 501-1000
option 1000+
.col-md-4.col-sm-6
.form-group
label.control-label(data-i18n="courses.number_total_students")
span.control-label(data-i18n="courses.number_total_students")
select.form-control(name="numStudentsTotal")
option(data-i18n="teachers_quote.num_students_default", value='')
option 1-500
@ -163,14 +170,11 @@ block content
option 10,000+
.form-group
.row
.col-md-offset-2.col-md-10
label.control-label(data-i18n="teachers_quote.education_level_label")
.help-block.small
em.text-info(data-i18n="teachers_quote.education_level_help")
.col-md-offset-2.col-md-5
.row.m-y-2
.col-md-offset-2.col-md-4
span.control-label(data-i18n="teachers_quote.education_level_label")
.help-block.small
em.text-info(data-i18n="teachers_quote.education_level_help")
.checkbox
label
input(type="checkbox" name="educationLevel" value="Elementary")
@ -196,7 +200,7 @@ block content
#anything-else-row.row
.col-md-offset-2.col-md-8
label.control-label
span.control-label
span(data-i18n="teachers_quote.anything_else")
span.spl.text-muted(data-i18n="signup.optional")
@ -210,7 +214,7 @@ block content
input(type="hidden" name="nces_students")
input(type="hidden" name="nces_phone")
#buttons-row.row.text-center
#buttons-row.row.m-y-2.text-center
input#submit-request-btn.btn.btn-lg.btn-primary(type="submit" data-i18n="[value]teachers_quote.title")
#form-submit-success.text-center(class=showDone ? '' : 'hide')
@ -219,7 +223,7 @@ block content
p
span.spr(data-i18n="teachers_quote.thanks_p")
a.spl(href="mailto:team@codecombat.com") team@codecombat.com
unless me.isAnonymous()
a.btn.btn-lg.btn-navy(href="/teachers/classes")
span(data-i18n='teachers_quote.back_to_classes')
@ -244,17 +248,17 @@ block content
.row
.col-md-offset-2.col-md-4
.form-group
label.control-label(data-i18n="general.username")
span.control-label(data-i18n="general.username")
input.form-control(name="name")
.row
.col-md-offset-2.col-md-4
.form-group
label.control-label(data-i18n="general.password")
span.control-label(data-i18n="general.password")
input.form-control(name="password1", type="password")
.col-md-4
.form-group
label.control-label(data-i18n="general.confirm_password")
span.control-label(data-i18n="general.confirm_password")
input.form-control(name="password2", type="password")
.text-center

View file

@ -1,6 +1,6 @@
RootView = require 'views/core/RootView'
CocoCollection = require 'collections/CocoCollection'
Campaigns = require 'collections/Campaigns'
Campaign = require 'models/Campaign'
Course = require 'models/Course'
module.exports = class AdminClassroomLevelsView extends RootView
@ -9,8 +9,8 @@ module.exports = class AdminClassroomLevelsView extends RootView
initialize: ->
return super() unless me.isAdmin()
@campaigns = new Campaigns()
@supermodel.trackRequest @campaigns.fetchByType('course', { data: { project: 'levels,levelsUpdated' } })
@campaigns = new CocoCollection([], { url: "/db/campaign", model: Campaign})
@supermodel.loadCollection(@campaigns, 'campaigns')
@courses = new CocoCollection([], { url: "/db/course", model: Course})
@supermodel.loadCollection(@courses, 'courses')
super()

View file

@ -56,6 +56,8 @@ require("locale/eo")
require("locale/uz")
require("locale/my")
require("locale/et")
require("locale/hr")
require("locale/mi")
module.exports = class DiplomatView extends ContributeClassView
id: 'diplomat-view'
@ -140,3 +142,5 @@ module.exports = class DiplomatView extends ContributeClassView
uz: [] # O'zbekcha, Uzbek
my: [] # က, Myanmar language
et: [] # Eesti, Estonian
hr: [] # hrvatski jezik, Croatian
mi: [] # te reo Māori, Māori

View file

@ -41,7 +41,10 @@ module.exports = class TeacherCoursesView extends RootView
@ownedClassrooms.fetchMine({data: {project: '_id'}})
@supermodel.trackCollection(@ownedClassrooms)
@courses = new Courses()
@supermodel.trackRequest @courses.fetchReleased()
if me.isAdmin()
@supermodel.trackRequest @courses.fetch()
else
@supermodel.trackRequest @courses.fetchReleased()
@campaigns = new Campaigns()
@supermodel.trackRequest @campaigns.fetchByType('course', { data: { project: 'levels,levelsUpdated' } })
@

View file

@ -365,12 +365,16 @@ module.exports = class CampaignView extends RootView
return experienceScore
createLine: (o1, o2) ->
p1 = x: o1.x, y: 0.66 * o1.y + 0.5
p2 = x: o2.x, y: 0.66 * o2.y + 0.5
mapHeight = parseFloat($(".map").css("height"))
mapWidth = parseFloat($(".map").css("width"))
return unless mapHeight > 0
ratio = mapWidth / mapHeight
p1 = x: o1.x, y: o1.y / ratio - 0.5
p2 = x: o2.x, y: o2.y / ratio
length = Math.sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y))
angle = Math.atan2(p1.y - p2.y, p2.x - p1.x) * 180 / Math.PI
transform = "rotate(#{angle}deg)"
line = $('<div>').appendTo('.map').addClass('next-level-line').css(transform: transform, width: length + '%', left: o1.x + '%', bottom: (o1.y + 0.5) + '%')
line = $('<div>').appendTo('.map').addClass('next-level-line').css(transform: transform, width: length + '%', left: o1.x + '%', bottom: (o1.y - 0.5) + '%')
line.append($('<div class="line">')).append($('<div class="point">'))
applyCampaignStyles: ->

View file

@ -7,9 +7,9 @@ errors = require 'core/errors'
User = require 'models/User'
ConfirmModal = require 'views/editor/modal/ConfirmModal'
algolia = require 'core/services/algolia'
NCES_KEYS = ['id', 'name', 'district', 'district_id', 'district_schools', 'district_students', 'students', 'phone']
FORM_KEY = 'request-quote-form'
DISTRICT_NCES_KEYS = ['district', 'district_id', 'district_schools', 'district_students', 'phone']
SCHOOL_NCES_KEYS = DISTRICT_NCES_KEYS.concat(['id', 'name', 'students'])
module.exports = class ConvertToTeacherAccountView extends RootView
id: 'convert-to-teacher-account-view'
@ -40,13 +40,13 @@ module.exports = class ConvertToTeacherAccountView extends RootView
return 'Your account has not been updated! If you continue, your changes will be lost.'
invalidateNCES: ->
for key in NCES_KEYS
for key in SCHOOL_NCES_KEYS
@$('input[name="nces_' + key + '"]').val ''
onLoaded: ->
if @trialRequests.size() and me.isTeacher()
return application.router.navigate('/teachers', { trigger: true, replace: true })
super()
afterRender: ->
@ -75,16 +75,34 @@ module.exports = class ConvertToTeacherAccountView extends RootView
"<div class='school'> #{hr.name.value} </div>" +
"<div class='district'>#{hr.district.value}, " +
"<span>#{hr.city?.value}, #{hr.state.value}</span></div>"
]).on 'autocomplete:selected', (event, suggestion, dataset) =>
@$('input[name="district"]').val suggestion.district
@$('input[name="city"]').val suggestion.city
@$('input[name="state"]').val suggestion.state
@$('input[name="district"]').val suggestion.district
@$('input[name="country"]').val 'USA'
for key in NCES_KEYS
for key in SCHOOL_NCES_KEYS
@$('input[name="nces_' + key + '"]').val suggestion[key]
@onChangeForm()
$("#district-control").algolia_autocomplete({hint: false}, [
source: (query, callback) ->
algolia.schoolsIndex.search(query, { hitsPerPage: 5, aroundLatLngViaIP: false }).then (answer) ->
callback answer.hits
, ->
callback []
displayKey: 'district',
templates:
suggestion: (suggestion) ->
hr = suggestion._highlightResult
"<div class='district'>#{hr.district.value}, " +
"<span>#{hr.city?.value}, #{hr.state.value}</span></div>"
]).on 'autocomplete:selected', (event, suggestion, dataset) =>
@$('input[name="organization"]').val '' # TODO: does not persist on tabbing: back to school, back to district
@$('input[name="city"]').val suggestion.city
@$('input[name="state"]').val suggestion.state
@$('input[name="country"]').val 'USA'
for key in DISTRICT_NCES_KEYS
@$('input[name="nces_' + key + '"]').val suggestion[key]
@onChangeForm()
onChangeForm: ->
@ -97,28 +115,35 @@ module.exports = class ConvertToTeacherAccountView extends RootView
form = @$('form')
attrs = forms.formToObject(form)
trialRequestAttrs = _.cloneDeep(attrs)
# Don't save n/a district entries, but do validate required district client-side
trialRequestAttrs = _.omit(trialRequestAttrs, 'district') if trialRequestAttrs.district?.replace(/\s/ig, '').match(/n\/a/ig)
if @$('#other-education-level-checkbox').is(':checked')
val = @$('#other-education-level-input').val()
attrs.educationLevel.push(val) if val
trialRequestAttrs.educationLevel.push(val) if val
forms.clearFormAlerts(form)
result = tv4.validateMultiple(attrs, formSchema)
result = tv4.validateMultiple(trialRequestAttrs, formSchema)
error = false
if not result.valid
forms.applyErrorsToForm(form, result.errors)
error = true
if not _.size(attrs.educationLevel)
forms.setErrorToProperty(form, 'educationLevel', 'Include at least one.')
if not _.size(trialRequestAttrs.educationLevel)
forms.setErrorToProperty(form, 'educationLevel', 'include at least one')
error = true
unless attrs.district
forms.setErrorToProperty(form, 'district', $.i18n.t('common.required_field'))
error = true
if error
forms.scrollToFirstError()
return
attrs['siteOrigin'] = 'convert teacher'
trialRequestAttrs['siteOrigin'] = 'convert teacher'
@trialRequest = new TrialRequest({
type: 'course'
properties: attrs
properties: trialRequestAttrs
})
if me.get('role') is 'student' and not me.isAnonymous()
modal = new ConfirmModal({
@ -151,15 +176,14 @@ module.exports = class ConvertToTeacherAccountView extends RootView
formSchema = {
type: 'object'
required: [
'firstName', 'lastName', 'organization', 'role', 'numStudents', 'city', 'state', 'country'
]
required: ['firstName', 'lastName', 'role', 'numStudents', 'city', 'state', 'country']
properties:
firstName: { type: 'string' }
lastName: { type: 'string' }
phoneNumber: { type: 'string' }
role: { type: 'string' }
organization: { type: 'string' }
district: { type: 'string' }
city: { type: 'string' }
state: { type: 'string' }
country: { type: 'string' }
@ -172,5 +196,5 @@ formSchema = {
notes: { type: 'string' }
}
for key in NCES_KEYS
for key in SCHOOL_NCES_KEYS
formSchema['nces_' + key] = type: 'string'

View file

@ -7,9 +7,9 @@ errors = require 'core/errors'
User = require 'models/User'
algolia = require 'core/services/algolia'
FORM_KEY = 'request-quote-form'
SIGNUP_REDIRECT = '/teachers/classes'
NCES_KEYS = ['id', 'name', 'district', 'district_id', 'district_schools', 'district_students', 'students', 'phone']
DISTRICT_NCES_KEYS = ['district', 'district_id', 'district_schools', 'district_students', 'phone']
SCHOOL_NCES_KEYS = DISTRICT_NCES_KEYS.concat(['id', 'name', 'students'])
module.exports = class CreateTeacherAccountView extends RootView
id: 'create-teacher-account-view'
@ -43,12 +43,12 @@ module.exports = class CreateTeacherAccountView extends RootView
super()
invalidateNCES: ->
for key in NCES_KEYS
for key in SCHOOL_NCES_KEYS
@$('input[name="nces_' + key + '"]').val ''
afterRender: ->
super()
# apply existing trial request on form
properties = @trialRequest.get('properties')
if properties
@ -58,7 +58,7 @@ module.exports = class CreateTeacherAccountView extends RootView
otherLevel = _.first(_.difference(submittedLevels, commonLevels)) or ''
@$('#other-education-level-checkbox').attr('checked', !!otherLevel)
@$('#other-education-level-input').val(otherLevel)
$("#organization-control").algolia_autocomplete({hint: false}, [
source: (query, callback) ->
algolia.schoolsIndex.search(query, { hitsPerPage: 5, aroundLatLngViaIP: false }).then (answer) ->
@ -72,16 +72,34 @@ module.exports = class CreateTeacherAccountView extends RootView
"<div class='school'> #{hr.name.value} </div>" +
"<div class='district'>#{hr.district.value}, " +
"<span>#{hr.city?.value}, #{hr.state.value}</span></div>"
]).on 'autocomplete:selected', (event, suggestion, dataset) =>
@$('input[name="district"]').val suggestion.district
@$('input[name="city"]').val suggestion.city
@$('input[name="state"]').val suggestion.state
@$('input[name="district"]').val suggestion.district
@$('input[name="country"]').val 'USA'
for key in NCES_KEYS
for key in SCHOOL_NCES_KEYS
@$('input[name="nces_' + key + '"]').val suggestion[key]
@onChangeForm()
$("#district-control").algolia_autocomplete({hint: false}, [
source: (query, callback) ->
algolia.schoolsIndex.search(query, { hitsPerPage: 5, aroundLatLngViaIP: false }).then (answer) ->
callback answer.hits
, ->
callback []
displayKey: 'district',
templates:
suggestion: (suggestion) ->
hr = suggestion._highlightResult
"<div class='district'>#{hr.district.value}, " +
"<span>#{hr.city?.value}, #{hr.state.value}</span></div>"
]).on 'autocomplete:selected', (event, suggestion, dataset) =>
@$('input[name="organization"]').val '' # TODO: does not persist on tabbing: back to school, back to district
@$('input[name="city"]').val suggestion.city
@$('input[name="state"]').val suggestion.state
@$('input[name="country"]').val 'USA'
for key in DISTRICT_NCES_KEYS
@$('input[name="nces_' + key + '"]').val suggestion[key]
@onChangeForm()
onClickLoginLink: ->
@ -95,35 +113,41 @@ module.exports = class CreateTeacherAccountView extends RootView
onSubmitForm: (e) ->
e.preventDefault()
# Creating Trial Request first, validate user attributes but do not use them
form = @$('form')
allAttrs = forms.formToObject(form)
trialRequestAttrs = _.omit(allAttrs, 'name', 'password1', 'password2')
# Don't save n/a district entries, but do validate required district client-side
trialRequestAttrs = _.omit(trialRequestAttrs, 'district') if trialRequestAttrs.district?.replace(/\s/ig, '').match(/n\/a/ig)
if @$('#other-education-level-checkbox').is(':checked')
val = @$('#other-education-level-input').val()
trialRequestAttrs.educationLevel.push(val) if val
forms.clearFormAlerts(form)
result = tv4.validateMultiple(trialRequestAttrs, formSchema)
error = false
if not result.valid
forms.applyErrorsToForm(form, result.errors)
error = true
if not forms.validateEmail(trialRequestAttrs.email)
forms.setErrorToProperty(form, 'email', 'Invalid email.')
if not error and not forms.validateEmail(trialRequestAttrs.email)
forms.setErrorToProperty(form, 'email', 'invalid email')
error = true
if not _.size(trialRequestAttrs.educationLevel)
forms.setErrorToProperty(form, 'educationLevel', 'Include at least one.')
forms.setErrorToProperty(form, 'educationLevel', 'include at least one')
error = true
unless allAttrs.district
forms.setErrorToProperty(form, 'district', $.i18n.t('common.required_field'))
error = true
unless @gplusAttrs or @facebookAttrs
if not allAttrs.password1
forms.setErrorToProperty(form, 'password1', 'Required field')
forms.setErrorToProperty(form, 'password1', $.i18n.t('common.required_field'))
error = true
else if not allAttrs.password2
forms.setErrorToProperty(form, 'password2', 'Required field')
forms.setErrorToProperty(form, 'password2', $.i18n.t('common.required_field'))
error = true
else if allAttrs.password1 isnt allAttrs.password2
forms.setErrorToProperty(form, 'password1', 'Password fields are not equivalent')
@ -151,7 +175,7 @@ module.exports = class CreateTeacherAccountView extends RootView
.addClass('has-error')
.append($("<div class='help-block error-help-block'>#{userExists} <a class='login-link'>#{logIn}</a>"))
forms.scrollToFirstError()
else
else
errors.showNotyNetworkError(arguments...)
onClickEmailExistsLoginLink: ->
@ -225,7 +249,6 @@ module.exports = class CreateTeacherAccountView extends RootView
@$('input[type="password"]').attr('disabled', true)
@$('#gplus-logged-in-row, #social-network-signups').toggleClass('hide')
# Facebook signup
onClickFacebookSignupButton: ->
@ -269,13 +292,9 @@ module.exports = class CreateTeacherAccountView extends RootView
@$('#facebook-logged-in-row, #social-network-signups').toggleClass('hide')
formSchema = {
type: 'object'
required: [
'firstName', 'lastName', 'email', 'organization', 'role', 'numStudents', 'city'
'state', 'country'
]
required: ['firstName', 'lastName', 'email', 'role', 'numStudents', 'city', 'state', 'country']
properties:
password1: { type: 'string' }
password2: { type: 'string' }
@ -286,6 +305,7 @@ formSchema = {
phoneNumber: { type: 'string' }
role: { type: 'string' }
organization: { type: 'string' }
district: { type: 'string' }
city: { type: 'string' }
state: { type: 'string' }
country: { type: 'string' }
@ -298,5 +318,5 @@ formSchema = {
notes: { type: 'string' }
}
for key in NCES_KEYS
for key in SCHOOL_NCES_KEYS
formSchema['nces_' + key] = type: 'string'

View file

@ -8,7 +8,8 @@ ConfirmModal = require 'views/editor/modal/ConfirmModal'
algolia = require 'core/services/algolia'
SIGNUP_REDIRECT = '/teachers'
NCES_KEYS = ['id', 'name', 'district', 'district_id', 'district_schools', 'district_students', 'students', 'phone']
DISTRICT_NCES_KEYS = ['district', 'district_id', 'district_schools', 'district_students', 'phone']
SCHOOL_NCES_KEYS = DISTRICT_NCES_KEYS.concat(['id', 'name', 'students'])
module.exports = class RequestQuoteView extends RootView
id: 'request-quote-view'
@ -46,7 +47,7 @@ module.exports = class RequestQuoteView extends RootView
super()
invalidateNCES: ->
for key in NCES_KEYS
for key in SCHOOL_NCES_KEYS
@$('input[name="nces_' + key + '"]').val ''
afterRender: ->
@ -75,16 +76,34 @@ module.exports = class RequestQuoteView extends RootView
"<div class='school'> #{hr.name.value} </div>" +
"<div class='district'>#{hr.district.value}, " +
"<span>#{hr.city?.value}, #{hr.state.value}</span></div>"
]).on 'autocomplete:selected', (event, suggestion, dataset) =>
@$('input[name="district"]').val suggestion.district
@$('input[name="city"]').val suggestion.city
@$('input[name="state"]').val suggestion.state
@$('input[name="district"]').val suggestion.district
@$('input[name="country"]').val 'USA'
for key in NCES_KEYS
for key in SCHOOL_NCES_KEYS
@$('input[name="nces_' + key + '"]').val suggestion[key]
@onChangeRequestForm()
$("#district-control").algolia_autocomplete({hint: false}, [
source: (query, callback) ->
algolia.schoolsIndex.search(query, { hitsPerPage: 5, aroundLatLngViaIP: false }).then (answer) ->
callback answer.hits
, ->
callback []
displayKey: 'district',
templates:
suggestion: (suggestion) ->
hr = suggestion._highlightResult
"<div class='district'>#{hr.district.value}, " +
"<span>#{hr.city?.value}, #{hr.state.value}</span></div>"
]).on 'autocomplete:selected', (event, suggestion, dataset) =>
@$('input[name="organization"]').val '' # TODO: does not persist on tabbing: back to school, back to district
@$('input[name="city"]').val suggestion.city
@$('input[name="state"]').val suggestion.state
@$('input[name="country"]').val 'USA'
for key in DISTRICT_NCES_KEYS
@$('input[name="nces_' + key + '"]').val suggestion[key]
@onChangeRequestForm()
onChangeRequestForm: ->
@ -96,32 +115,39 @@ module.exports = class RequestQuoteView extends RootView
e.preventDefault()
form = @$('#request-form')
attrs = forms.formToObject(form)
trialRequestAttrs = _.cloneDeep(attrs)
# Don't save n/a district entries, but do validate required district client-side
trialRequestAttrs = _.omit(trialRequestAttrs, 'district') if trialRequestAttrs.district?.replace(/\s/ig, '').match(/n\/a/ig)
# custom other input logic
if @$('#other-education-level-checkbox').is(':checked')
val = @$('#other-education-level-input').val()
attrs.educationLevel.push(val) if val
trialRequestAttrs.educationLevel.push(val) if val
forms.clearFormAlerts(form)
requestFormSchema = if me.isAnonymous() then requestFormSchemaAnonymous else requestFormSchemaLoggedIn
result = tv4.validateMultiple(attrs, requestFormSchemaAnonymous)
result = tv4.validateMultiple(trialRequestAttrs, requestFormSchemaAnonymous)
error = false
if not result.valid
forms.applyErrorsToForm(form, result.errors)
error = true
if not forms.validateEmail(attrs.email)
forms.setErrorToProperty(form, 'email', 'Invalid email.')
if not error and not forms.validateEmail(trialRequestAttrs.email)
forms.setErrorToProperty(form, 'email', 'invalid email')
error = true
if not _.size(attrs.educationLevel)
forms.setErrorToProperty(form, 'educationLevel', 'Include at least one.')
if not _.size(trialRequestAttrs.educationLevel)
forms.setErrorToProperty(form, 'educationLevel', 'include at least one')
error = true
unless attrs.district
forms.setErrorToProperty(form, 'district', $.i18n.t('common.required_field'))
error = true
if error
forms.scrollToFirstError()
return
attrs['siteOrigin'] = 'demo request'
trialRequestAttrs['siteOrigin'] = 'demo request'
@trialRequest = new TrialRequest({
type: 'course'
properties: attrs
properties: trialRequestAttrs
})
if me.get('role') is 'student' and not me.isAnonymous()
modal = new ConfirmModal({
@ -262,7 +288,7 @@ module.exports = class RequestQuoteView extends RootView
requestFormSchemaAnonymous = {
type: 'object'
required: [
'firstName', 'lastName', 'email', 'organization', 'role', 'purchaserRole', 'numStudents',
'firstName', 'lastName', 'email', 'role', 'purchaserRole', 'numStudents',
'numStudentsTotal', 'phoneNumber', 'city', 'state', 'country']
properties:
firstName: { type: 'string' }
@ -273,6 +299,7 @@ requestFormSchemaAnonymous = {
role: { type: 'string' }
purchaserRole: { type: 'string' }
organization: { type: 'string' }
district: { type: 'string' }
city: { type: 'string' }
state: { type: 'string' }
country: { type: 'string' }
@ -285,7 +312,7 @@ requestFormSchemaAnonymous = {
notes: { type: 'string' },
}
for key in NCES_KEYS
for key in SCHOOL_NCES_KEYS
requestFormSchemaAnonymous['nces_' + key] = type: 'string'
# same form, but add username input

View file

@ -34,7 +34,8 @@ module.exports = class TeachersContactModal extends ModalView
message = """
Hi CodeCombat! I want to learn more about the Classroom experience and get licenses so that my students can access Computer Science 2 and on.
Name of School/District: #{props.organization or ''}
Name of School #{props.nces_name or props.organization or ''}
Name of District: #{props.nces_district or props.district or ''}
Role: #{props.role or ''}
Phone Number: #{props.phoneNumber or ''}
"""

View file

@ -16,7 +16,7 @@ if (process.argv.length !== 10) {
// TODO: Cleanup country/status lookup code
// Save as custom fields instead of user-specific lead notes (also saving nces_ props)
const commonTrialProperties = ['organization', 'city', 'state', 'country'];
const commonTrialProperties = ['organization', 'district', 'city', 'state', 'country'];
// Old properties which are deprecated or moved
const customFieldsToRemove = [
@ -327,7 +327,7 @@ function findCocoLeads(done) {
if (!trialRequest.properties || !trialRequest.properties.email) continue;
const email = trialRequest.properties.email.toLowerCase();
emails.push(email);
const name = trialRequest.properties.nces_name || trialRequest.properties.organization || trialRequest.properties.school || email;
const name = trialRequest.properties.nces_name || trialRequest.properties.organization || trialRequest.properties.school || trialRequest.properties.district || trialRequest.properties.nces_district || email;
if (!leads[name]) leads[name] = new CocoLead(name);
leads[name].addTrialRequest(email, trialRequest);
emailLeadMap[email] = leads[name];

View file

@ -105,13 +105,13 @@ module.exports =
members = classroom.get('members') or []
members = members.slice(memberSkip, memberSkip + memberLimit)
dbqs = []
select = 'state.complete level creator playtime changed dateFirstCompleted submitted'
select = 'state.complete level creator playtime changed created dateFirstCompleted submitted'
for member in members
dbqs.push(LevelSession.find({creator: member.toHexString()}).select(select).exec())
results = yield dbqs
sessions = _.flatten(results)
res.status(200).send(sessions)
fetchMembers: wrap (req, res, next) ->
throw new errors.Unauthorized() unless req.user
memberLimit = parse.getLimitFromReq(req, {default: 10, max: 100, param: 'memberLimit'})

View file

@ -11,10 +11,6 @@ cutoffDate = new Date(2015,11,11)
cutoffID = mongoose.Types.ObjectId(Math.floor(cutoffDate/1000).toString(16)+'0000000000000000')
module.exports =
logError: (user, msg) ->
console.warn "Prepaid Error: [#{user.get('slug')} (#{user._id})] '#{msg}'"
post: wrap (req, res) ->
validTypes = ['course']
unless req.body.type in validTypes
@ -71,7 +67,7 @@ module.exports =
update = { $push: { redeemers : { date: new Date(), userID: user._id } }}
result = yield Prepaid.update(query, update)
if result.nModified is 0
@logError(req.user, "POST prepaid redeemer lost race on maxRedeemers")
console.error("Prepaid redeem error: [#{req.user.get('slug')} (#{req.user._id})] 'POST prepaid redeemer lost race on maxRedeemers'")
throw new errors.Forbidden('This prepaid is exhausted')
update = {
@ -142,7 +138,7 @@ module.exports =
trialRequests = yield TrialRequest.find({$and: [{type: 'course'}, {applicant: {$in: userIDs}}]}, {applicant: 1, properties: 1}).lean()
schoolPrepaidsMap = {}
for trialRequest in trialRequests
school = trialRequest.properties?.organization ? trialRequest.properties?.school
school = trialRequest.properties?.nces_name ? trialRequest.properties?.organization ? trialRequest.properties?.school
continue unless school
if userPrepaidsMap[trialRequest.applicant.valueOf()]?.length > 0
schoolPrepaidsMap[school] ?= []

View file

@ -184,6 +184,7 @@ module.exports = {
email: 'an@email.com'
phoneNumber: '555-555-5555'
organization: 'Greendale'
district: 'Green District'
}
}, attrs)
}

View file

@ -31,6 +31,7 @@ describe 'ConvertToTeacherAccountView (/teachers/update-account)', ->
phoneNumber: '555-555-5555'
role: 'Teacher'
organization: 'School'
district: 'District'
city: 'Springfield'
state: 'AA'
country: 'asdf'
@ -168,3 +169,48 @@ describe 'ConvertToTeacherAccountView (/teachers/update-account)', ->
})
expect(me.get('role')).toBe(successForm.role.toLowerCase())
describe 'submitting the form without school', ->
beforeEach ->
view.$el.find('#request-form').trigger('change') # to confirm navigating away isn't prevented
form = view.$('form')
formData = _.omit(successForm, ['organization'])
forms.objectToForm(form, formData)
form.submit()
it 'submits a trial request, which does not include school setting', ->
request = jasmine.Ajax.requests.mostRecent()
expect(request.url).toBe('/db/trial.request')
expect(request.method).toBe('POST')
attrs = JSON.parse(request.params)
expect(attrs.properties?.organization).toBeUndefined()
expect(attrs.properties?.district).toEqual('District')
describe 'submitting the form without district', ->
beforeEach ->
view.$el.find('#request-form').trigger('change') # to confirm navigating away isn't prevented
form = view.$('form')
formData = _.omit(successForm, ['district'])
forms.objectToForm(form, formData)
form.submit()
it 'displays a validation error on district and not school', ->
expect(view.$('#organization-control').parent().hasClass('has-error')).toEqual(false)
expect(view.$('#district-control').parent().hasClass('has-error')).toEqual(true)
describe 'submitting the form district set to n/a', ->
beforeEach ->
view.$el.find('#request-form').trigger('change') # to confirm navigating away isn't prevented
form = view.$('form')
formData = _.omit(successForm, ['organization'])
formData.district = 'N/A'
forms.objectToForm(form, formData)
form.submit()
it 'submits a trial request, which does not include district setting', ->
expect(view.$('#organization-control').parent().hasClass('has-error')).toEqual(false)
expect(view.$('#district-control').parent().hasClass('has-error')).toEqual(false)
request = jasmine.Ajax.requests.mostRecent()
expect(request.url).toBe('/db/trial.request')
expect(request.method).toBe('POST')
attrs = JSON.parse(request.params)
expect(attrs.properties?.district).toBeUndefined()

View file

@ -33,6 +33,7 @@ describe 'CreateTeacherAccountView', ->
phoneNumber: '555-555-5555'
role: 'Teacher'
organization: 'School'
district: 'District'
city: 'Springfield'
state: 'AA'
country: 'asdf'
@ -219,6 +220,8 @@ describe 'CreateTeacherAccountView', ->
expect(attrs.password2).toBeUndefined()
expect(attrs.name).toBeUndefined()
expect(attrs.properties?.siteOrigin).toBe('create teacher')
expect(attrs.properties?.organization).toEqual('School')
expect(attrs.properties?.district).toEqual('District')
describe 'after saving the new trial request', ->
beforeEach ->
@ -266,5 +269,49 @@ describe 'CreateTeacherAccountView', ->
spyOn(view, 'openModalView')
view.$('#email-form-group .login-link').click()
expect(view.openModalView).toHaveBeenCalled()
describe 'submitting the form without school', ->
beforeEach ->
view.$el.find('#request-form').trigger('change') # to confirm navigating away isn't prevented
form = view.$('form')
formData = _.omit(successForm, ['organization'])
forms.objectToForm(form, formData)
form.submit()
it 'submits a trial request, which does not include school setting', ->
request = jasmine.Ajax.requests.mostRecent()
expect(request.url).toBe('/db/trial.request')
expect(request.method).toBe('POST')
attrs = JSON.parse(request.params)
expect(attrs.properties?.organization).toBeUndefined()
expect(attrs.properties?.district).toEqual('District')
describe 'submitting the form without district', ->
beforeEach ->
view.$el.find('#request-form').trigger('change') # to confirm navigating away isn't prevented
form = view.$('form')
formData = _.omit(successForm, ['district'])
forms.objectToForm(form, formData)
form.submit()
it 'displays a validation error on district and not school', ->
expect(view.$('#organization-control').parent().hasClass('has-error')).toEqual(false)
expect(view.$('#district-control').parent().hasClass('has-error')).toEqual(true)
describe 'submitting the form district set to n/a', ->
beforeEach ->
view.$el.find('#request-form').trigger('change') # to confirm navigating away isn't prevented
form = view.$('form')
formData = _.omit(successForm, ['organization'])
formData.district = 'N/A'
forms.objectToForm(form, formData)
form.submit()
it 'submits a trial request, which does not include district setting', ->
expect(view.$('#organization-control').parent().hasClass('has-error')).toEqual(false)
expect(view.$('#district-control').parent().hasClass('has-error')).toEqual(false)
request = jasmine.Ajax.requests.mostRecent()
expect(request.url).toBe('/db/trial.request')
expect(request.method).toBe('POST')
attrs = JSON.parse(request.params)
expect(attrs.properties?.district).toBeUndefined()

View file

@ -2,16 +2,17 @@ RequestQuoteView = require 'views/teachers/RequestQuoteView'
forms = require 'core/forms'
describe 'RequestQuoteView', ->
view = null
successFormValues = {
successForm = {
firstName: 'A'
lastName: 'B'
email: 'C@D.com'
phoneNumber: '555-555-5555'
role: 'Teacher'
organization: 'School'
district: 'District'
city: 'Springfield'
state: 'AA'
country: 'asdf'
@ -20,10 +21,10 @@ describe 'RequestQuoteView', ->
purchaserRole: 'Approve Funds'
educationLevel: ['Middle']
}
isSubmitRequest = (r) -> _.string.startsWith(r.url, '/db/trial.request') and r.method is 'POST'
describe 'when user is anonymous and has an existing trial request', ->
describe 'when an anonymous user', ->
beforeEach (done) ->
me.clear()
me.set('_id', '1234')
@ -32,184 +33,220 @@ describe 'RequestQuoteView', ->
view = new RequestQuoteView()
view.render()
jasmine.demoEl(view.$el)
request = jasmine.Ajax.requests.mostRecent()
request.respondWith({
status: 200
responseText: JSON.stringify([{
_id: '1'
properties: {
firstName: 'First'
lastName: 'Last'
}
}])
})
_.defer done # Let SuperModel finish
it 'shows request received', ->
expect(view.$('#request-form').hasClass('hide')).toBe(true)
expect(view.$('#form-submit-success').hasClass('hide')).toBe(false)
describe 'when user is signed in and has an existing trial request', ->
beforeEach (done) ->
me.clear()
me.set('_id', '1234')
me._revertAttributes = {}
spyOn(me, 'isAnonymous').and.returnValue(false)
view = new RequestQuoteView()
view.render()
jasmine.demoEl(view.$el)
request = jasmine.Ajax.requests.mostRecent()
request.respondWith({
status: 200
responseText: JSON.stringify([{
_id: '1'
properties: {
firstName: 'First'
lastName: 'Last'
}
}])
})
_.defer done # Let SuperModel finish
it 'shows form with data from the most recent request', ->
expect(view.$('input[name="firstName"]').val()).toBe('First')
describe 'when a user is anonymous and does NOT have an existing trial request', ->
beforeEach (done) ->
me.clear()
me.set('_id', '1234')
me._revertAttributes = {}
spyOn(me, 'isAnonymous').and.returnValue(true)
view = new RequestQuoteView()
view.render()
jasmine.demoEl(view.$el)
request = jasmine.Ajax.requests.mostRecent()
request.respondWith({
status: 200
responseText: '[]'
})
_.defer done # Let SuperModel finish
describe 'when the form is unchanged', ->
it 'does not prevent navigating away', ->
expect(_.result(view, 'onLeaveMessage')).toBeFalsy()
describe 'when the form has changed but is not submitted', ->
beforeEach ->
view.$el.find('#request-form').trigger('change')
it 'prevents navigating away', ->
expect(_.result(view, 'onLeaveMessage')).toBeTruthy()
describe 'on successful form submit', ->
beforeEach ->
view.$el.find('#request-form').trigger('change') # to confirm navigating away isn't prevented
forms.objectToForm(view.$el, successFormValues)
view.$('#request-form').submit()
@submitRequest = _.last(jasmine.Ajax.requests.filter(isSubmitRequest))
@submitRequest.respondWith({
status: 201
responseText: JSON.stringify(_.extend({_id: 'a'}, successFormValues))
describe 'has an existing trial request', ->
beforeEach (done) ->
request = jasmine.Ajax.requests.mostRecent()
request.respondWith({
status: 200
responseText: JSON.stringify([{
_id: '1'
properties: {
firstName: 'First'
lastName: 'Last'
}
}])
})
_.defer done # Let SuperModel finish
it 'does not prevent navigating away', ->
expect(_.result(view, 'onLeaveMessage')).toBeFalsy()
it 'creates a new trial request', ->
expect(@submitRequest).toBeTruthy()
expect(@submitRequest.method).toBe('POST')
attrs = JSON.parse(@submitRequest.params)
expect(attrs.properties?.siteOrigin).toBe('demo request')
it 'sets the user\'s role to the one they chose', ->
request = _.last(jasmine.Ajax.requests.filter((r) -> _.string.startsWith(r.url, '/db/user')))
expect(request).toBeTruthy()
expect(request.method).toBe('PUT')
expect(JSON.parse(request.params).role).toBe('teacher')
it 'shows a signup form', ->
expect(view.$('#form-submit-success').hasClass('hide')).toBe(false)
it 'shows request received', ->
expect(view.$('#request-form').hasClass('hide')).toBe(true)
describe 'signup form', ->
expect(view.$('#form-submit-success').hasClass('hide')).toBe(false)
describe 'does NOT have an existing trial request', ->
beforeEach (done) ->
request = jasmine.Ajax.requests.mostRecent()
request.respondWith({
status: 200
responseText: '[]'
})
_.defer done # Let SuperModel finish
describe 'when the form is unchanged', ->
it 'does not prevent navigating away', ->
expect(_.result(view, 'onLeaveMessage')).toBeFalsy()
describe 'when the form has changed but is not submitted', ->
beforeEach ->
application.facebookHandler.fakeAPI()
application.gplusHandler.fakeAPI()
it 'fills the username field with the given first and last names', ->
expect(view.$('input[name="name"]').val()).toBe('A B')
it 'includes a facebook button which will sign them in immediately', ->
view.$('#facebook-signup-btn').click()
request = jasmine.Ajax.requests.mostRecent()
expect(request.method).toBe('PUT')
expect(request.url).toBe('/db/user?facebookID=abcd&facebookAccessToken=1234')
it 'includes a gplus button which will sign them in immediately', ->
view.$('#gplus-signup-btn').click()
request = jasmine.Ajax.requests.mostRecent()
expect(request.method).toBe('PUT')
expect(request.url).toBe('/db/user?gplusID=abcd&gplusAccessToken=1234')
it 'can sign them up with username and password', ->
form = view.$('#signup-form')
forms.objectToForm(form, {
password1: 'asdf'
password2: 'asdf'
name: 'some name'
view.$el.find('#request-form').trigger('change')
it 'prevents navigating away', ->
expect(_.result(view, 'onLeaveMessage')).toBeTruthy()
describe 'on successful form submit', ->
beforeEach ->
view.$el.find('#request-form').trigger('change') # to confirm navigating away isn't prevented
forms.objectToForm(view.$el, successForm)
view.$('#request-form').submit()
@submitRequest = _.last(jasmine.Ajax.requests.filter(isSubmitRequest))
@submitRequest.respondWith({
status: 201
responseText: JSON.stringify(_.extend({_id: 'a'}, successForm))
})
form.submit()
request = jasmine.Ajax.requests.mostRecent()
it 'does not prevent navigating away', ->
expect(_.result(view, 'onLeaveMessage')).toBeFalsy()
it 'creates a new trial request', ->
expect(@submitRequest).toBeTruthy()
expect(@submitRequest.method).toBe('POST')
attrs = JSON.parse(@submitRequest.params)
expect(attrs.properties?.siteOrigin).toBe('demo request')
it 'sets the user\'s role to the one they chose', ->
request = _.last(jasmine.Ajax.requests.filter((r) -> _.string.startsWith(r.url, '/db/user')))
expect(request).toBeTruthy()
expect(request.method).toBe('PUT')
expect(request.url).toBe('/db/user/1234')
describe 'when an anonymous user tries to submit a request with an existing user\'s email', ->
expect(JSON.parse(request.params).role).toBe('teacher')
it 'shows a signup form', ->
expect(view.$('#form-submit-success').hasClass('hide')).toBe(false)
expect(view.$('#request-form').hasClass('hide')).toBe(true)
describe 'signup form', ->
beforeEach ->
application.facebookHandler.fakeAPI()
application.gplusHandler.fakeAPI()
it 'fills the username field with the given first and last names', ->
expect(view.$('input[name="name"]').val()).toBe('A B')
it 'includes a facebook button which will sign them in immediately', ->
view.$('#facebook-signup-btn').click()
request = jasmine.Ajax.requests.mostRecent()
expect(request.method).toBe('PUT')
expect(request.url).toBe('/db/user?facebookID=abcd&facebookAccessToken=1234')
it 'includes a gplus button which will sign them in immediately', ->
view.$('#gplus-signup-btn').click()
request = jasmine.Ajax.requests.mostRecent()
expect(request.method).toBe('PUT')
expect(request.url).toBe('/db/user?gplusID=abcd&gplusAccessToken=1234')
it 'can sign them up with username and password', ->
form = view.$('#signup-form')
forms.objectToForm(form, {
password1: 'asdf'
password2: 'asdf'
name: 'some name'
})
form.submit()
request = jasmine.Ajax.requests.mostRecent()
expect(request.method).toBe('PUT')
expect(request.url).toBe('/db/user/1234')
describe 'tries to submit a request with an existing user\'s email', ->
beforeEach ->
forms.objectToForm(view.$el, successFormValues)
forms.objectToForm(view.$el, successForm)
view.$('#request-form').submit()
@submitRequest = _.last(jasmine.Ajax.requests.filter(isSubmitRequest))
@submitRequest.respondWith({
status: 409
responseText: '{}'
})
it 'shows an error that the email already exists', ->
expect(view.$('#email-form-group').hasClass('has-error')).toBe(true)
expect(view.$('#email-form-group .error-help-block').length).toBe(1)
describe 'when user is signed in and has role "student"', ->
describe 'submits the form without school', ->
beforeEach ->
view.$el.find('#request-form').trigger('change') # to confirm navigating away isn't prevented
form = view.$('#request-form')
formData = _.omit(successForm, ['organization'])
forms.objectToForm(form, formData)
form.submit()
it 'submits a trial request, which does not include school setting', ->
request = jasmine.Ajax.requests.mostRecent()
expect(request.url).toBe('/db/trial.request')
expect(request.method).toBe('POST')
attrs = JSON.parse(request.params)
expect(attrs.properties?.organization).toBeUndefined()
expect(attrs.properties?.district).toEqual('District')
describe 'submits the form without district', ->
beforeEach ->
view.$el.find('#request-form').trigger('change') # to confirm navigating away isn't prevented
form = view.$('#request-form')
formData = _.omit(successForm, ['district'])
forms.objectToForm(form, formData)
form.submit()
it 'displays a validation error on district and not school', ->
expect(view.$('#organization-control').parent().hasClass('has-error')).toEqual(false)
expect(view.$('#district-control').parent().hasClass('has-error')).toEqual(true)
describe 'submits form with district set to n/a', ->
beforeEach ->
view.$el.find('#request-form').trigger('change') # to confirm navigating away isn't prevented
form = view.$('#request-form')
formData = _.omit(successForm, ['organization'])
formData.district = 'N/A'
forms.objectToForm(form, formData)
form.submit()
it 'submits a trial request, which does not include district setting', ->
expect(view.$('#organization-control').parent().hasClass('has-error')).toEqual(false)
expect(view.$('#district-control').parent().hasClass('has-error')).toEqual(false)
request = jasmine.Ajax.requests.mostRecent()
expect(request.url).toBe('/db/trial.request')
expect(request.method).toBe('POST')
attrs = JSON.parse(request.params)
expect(attrs.properties?.district).toBeUndefined()
describe 'when a signed in user', ->
beforeEach (done) ->
me.set('role', 'student')
me.set('name', 'Some User')
me.clear()
me.set('_id', '1234')
me._revertAttributes = {}
spyOn(me, 'isAnonymous').and.returnValue(false)
view = new RequestQuoteView()
view.render()
jasmine.demoEl(view.$el)
request = jasmine.Ajax.requests.mostRecent()
request.respondWith({ status: 200, responseText: '[]'})
_.defer done # Let SuperModel finish
it 'shows a conversion warning', ->
expect(view.$('#conversion-warning').length).toBe(1)
it 'requires confirmation to submit the form', ->
form = view.$('#request-form')
forms.objectToForm(form, successFormValues)
spyOn(view, 'openModalView')
form.submit()
expect(view.openModalView).toHaveBeenCalled()
submitRequest = _.last(jasmine.Ajax.requests.filter(isSubmitRequest))
expect(submitRequest).toBeFalsy()
confirmModal = view.openModalView.calls.argsFor(0)[0]
confirmModal.trigger 'confirm'
submitRequest = _.last(jasmine.Ajax.requests.filter(isSubmitRequest))
expect(submitRequest).toBeTruthy()
describe 'has an existing trial request', ->
beforeEach (done) ->
request = jasmine.Ajax.requests.mostRecent()
request.respondWith({
status: 200
responseText: JSON.stringify([{
_id: '1'
properties: {
firstName: 'First'
lastName: 'Last'
}
}])
})
_.defer done # Let SuperModel finish
it 'shows form with data from the most recent request', ->
expect(view.$('input[name="firstName"]').val()).toBe('First')
describe 'has role "student"', ->
beforeEach (done) ->
me.clear()
me.set('role', 'student')
me.set('name', 'Some User')
request = jasmine.Ajax.requests.mostRecent()
request.respondWith({ status: 200, responseText: '[]'})
_.defer done # Let SuperModel finish
it 'shows a conversion warning', ->
expect(view.$('#conversion-warning').length).toBe(1)
it 'requires confirmation to submit the form', ->
form = view.$('#request-form')
forms.objectToForm(form, successForm)
spyOn(view, 'openModalView')
form.submit()
expect(view.openModalView).toHaveBeenCalled()
submitRequest = _.last(jasmine.Ajax.requests.filter(isSubmitRequest))
expect(submitRequest).toBeFalsy()
confirmModal = view.openModalView.calls.argsFor(0)[0]
confirmModal.trigger 'confirm'
submitRequest = _.last(jasmine.Ajax.requests.filter(isSubmitRequest))
expect(submitRequest).toBeTruthy()