mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-03-24 11:50:58 -04:00
Merge branch 'master' into production
This commit is contained in:
commit
bb3d9fa05a
21 changed files with 200 additions and 119 deletions
README.md
app
core
lib/world
locale
styles/courses
templates
views/courses
scripts/mongodb
server
test/server/functional
|
@ -14,7 +14,7 @@ We've made it easy to fork the project, run a simple script that'll install all
|
|||
|
||||
### [Getting In Touch](https://github.com/codecombat/codecombat/wiki/Developer-organization)
|
||||
|
||||
Whether you're novice or pro, the CodeCombat team is ready to help you implement your ideas. Reach out on our [forum](http://discourse.codecombat.com), our [issue tracker](https://github.com/codecombat/codecombat/issues), or our [developer chat room](https://www.hipchat.com/g3plnOKqa), or see the docs for [more on how to contribute](https://github.com/codecombat/codecombat/wiki/Developer-organization).
|
||||
Whether you're novice or pro, the CodeCombat team is ready to help you implement your ideas. Reach out on our [forum](http://discourse.codecombat.com), our [issue tracker](https://github.com/codecombat/codecombat/issues), or our [developer chat room](https://www.hipchat.com/gkaufqwnj), or see the docs for [more on how to contribute](https://github.com/codecombat/codecombat/wiki/Developer-organization).
|
||||
|
||||
### [License](https://github.com/codecombat/codecombat/blob/master/LICENSE)
|
||||
|
||||
|
|
|
@ -65,6 +65,8 @@ module.exports = class CocoRouter extends Backbone.Router
|
|||
'courses/mock1/enroll/:courseID': go('courses/mock1/CourseEnrollView')
|
||||
'courses/mock1/:courseID': go('courses/mock1/CourseDetailsView')
|
||||
'courses': go('courses/CoursesView')
|
||||
'courses/students': go('courses/CoursesView')
|
||||
'courses/teachers': go('courses/CoursesView')
|
||||
'courses/enroll(/:courseID)': go('courses/CourseEnrollView')
|
||||
'courses/:courseID(/:courseInstanceID)': go('courses/CourseDetailsView')
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ module.exports.thangNames = thangNames =
|
|||
'Shmeal'
|
||||
'Upfish'
|
||||
'Yugark'
|
||||
'Shema'
|
||||
]
|
||||
'Ogre Munchkin M': [
|
||||
# Male
|
||||
|
@ -50,6 +51,7 @@ module.exports.thangNames = thangNames =
|
|||
'Weeb'
|
||||
'Yart'
|
||||
'Zozo'
|
||||
'Zock'
|
||||
]
|
||||
'Ogre Thrower': [
|
||||
# Female
|
||||
|
@ -90,6 +92,7 @@ module.exports.thangNames = thangNames =
|
|||
'Taric'
|
||||
'Vaelia'
|
||||
'Antary'
|
||||
'Femae'
|
||||
]
|
||||
'Ogre Witch': [
|
||||
# Female
|
||||
|
@ -121,12 +124,14 @@ module.exports.thangNames = thangNames =
|
|||
'Ganju'
|
||||
'Hopper'
|
||||
'Ralthora'
|
||||
'Yugorota'
|
||||
]
|
||||
'Burl': [
|
||||
# Animal
|
||||
'Borlit'
|
||||
'Burlosh'
|
||||
'Dorf'
|
||||
'Teemer'
|
||||
]
|
||||
'Sand Yak': [
|
||||
# Animal
|
||||
|
@ -468,6 +473,7 @@ module.exports.thangNames = thangNames =
|
|||
'Warshall'
|
||||
'Yue Fei'
|
||||
'Zhou Tong'
|
||||
'Archy'
|
||||
]
|
||||
'Peasant M': [
|
||||
# Male
|
||||
|
@ -497,6 +503,7 @@ module.exports.thangNames = thangNames =
|
|||
'Winkler'
|
||||
'Yorik'
|
||||
'Yusef'
|
||||
'Yoltovic'
|
||||
]
|
||||
'Peasant F': [
|
||||
# Female
|
||||
|
@ -522,6 +529,7 @@ module.exports.thangNames = thangNames =
|
|||
'Ruth'
|
||||
'Tabitha'
|
||||
'Thea'
|
||||
'Lea'
|
||||
]
|
||||
'Soldier M': [
|
||||
# Male
|
||||
|
@ -648,6 +656,7 @@ module.exports.thangNames = thangNames =
|
|||
'Randy'
|
||||
'Raymond'
|
||||
'Remy'
|
||||
'Rex'
|
||||
'Ricardo'
|
||||
'Richard'
|
||||
'Robert'
|
||||
|
|
|
@ -294,7 +294,7 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip
|
|||
infinite_loop_reset_level: "Reiniciar Nivel"
|
||||
infinite_loop_comment_out: "Comente Mi Código"
|
||||
tip_toggle_play: "Activa jugar/pausa con Ctrl+P."
|
||||
tip_scrub_shortcut: "Ctrl+[ y Ctrl+] para rebobinar y avanzar rápido."
|
||||
tip_scrub_shortcut: "Ctrl+[ y Ctrl+] para rebobinar y avanzar rápido."
|
||||
tip_guide_exists: "Haga click en la guía en la parte superior de la página para obtener información útil"
|
||||
tip_open_source: "¡CodeCombat es 100% código abierto!"
|
||||
tip_tell_friends: "¿Disfrutando de CodeCombat? ¡Cuéntale a tus amigos acerca de nosotros!"
|
||||
|
@ -658,17 +658,17 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip
|
|||
sys_requirements_2: "CodeCombat no está soportado en iPad aún."
|
||||
|
||||
teachers_survey:
|
||||
title: "Encuesta para profesores"
|
||||
must_be_logged: "Debe iniciar sesión primero. Por favor cree una cuenta o inicie sesión desde el menú en la parte superior."
|
||||
title: "Encuesta de Maestros"
|
||||
must_be_logged: "Debes ingresar primero. Por favor, crea una cuenta o ingresa desde el menú de arriba."
|
||||
retrieving: "Obteniendo información..."
|
||||
being_reviewed_1: "Su solicitud para una prueba gratuita de subscripción está siendo"
|
||||
being_reviewed_1: "Su aplicación a una suscripción gratuita está siendo"
|
||||
being_reviewed_2: "revisada."
|
||||
approved_1: "Su solicitud para una prueba gratuita de subscripción fue"
|
||||
approved_2: "Aprobada."
|
||||
approved_3: "Instruccciones posteriores han sido enviadas a"
|
||||
denied_1: "Su solicitud para una prueba gratuita de subscripción fue"
|
||||
approved_1: "Su aplicación a una suscripción gratuita fue"
|
||||
approved_2: "aprobada."
|
||||
approved_3: "Más instrucciones han sido enviadas a"
|
||||
denied_1: "Su aplicación a una suscripción gratuita ha sido"
|
||||
denied_2: "denegada."
|
||||
contact_1: "Por favor contáctenos"
|
||||
contact_1: "Por favor contactarse"
|
||||
contact_2: "si tiene más preguntas."
|
||||
description_1: "Ofrecemos suscripciones gratuitas a maestros con propósitos de evaluación. Puede hallar más información en nuestra"
|
||||
description_2: "página"
|
||||
|
@ -677,13 +677,13 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip
|
|||
email: "Dirección de email"
|
||||
school: "Nombre del colegio"
|
||||
location: "Nombre de la ciudad"
|
||||
age_students: "¿Qué edad tienen sus estudiantes?"
|
||||
under: "Menor"
|
||||
other: "Otro:"
|
||||
amount_students: "¿A cuantos alumnos les enseña?"
|
||||
hear_about: "¿Donde escuchó sobre CodeCombat?"
|
||||
fill_fields: "Porfavor llene todos los campos."
|
||||
thanks: "Gracias! Vamos a mandarle instrucciónes para iniciar proximamente."
|
||||
age_students: "¿Qué edad tienen tus estudiantes?"
|
||||
under: "Bajo"
|
||||
other: "Otros:"
|
||||
amount_students: "¿A cuantos alumnos le enseña?"
|
||||
hear_about: "¿Donde escuchaste acerca de CodeCombat?"
|
||||
fill_fields: "Por favor llene todos los campos."
|
||||
thanks: "¡Gracias! Pronto te enviaremos instrucciones para iniciar."
|
||||
|
||||
versions:
|
||||
save_version_title: "Guardar nueva versión"
|
||||
|
@ -693,7 +693,7 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip
|
|||
cla_url: "CLA"
|
||||
cla_suffix: "."
|
||||
cla_agree: "ACEPTO"
|
||||
owner_approve: "Un dueño necesitará aprobarlo antes de que tus cambios puedan ser visibles."
|
||||
owner_approve: "Necesita la aprobación de un propietario para que los cambios sean visibles."
|
||||
|
||||
contact:
|
||||
contact_us: "Contacta a CodeCombat"
|
||||
|
@ -730,8 +730,8 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip
|
|||
admin: "Admin"
|
||||
new_password: "Nueva Contraseña"
|
||||
new_password_verify: "Verificar"
|
||||
type_in_email: "Ingrese su correo electrónico para confirmar la eliminación" # {change}
|
||||
type_in_password: "También, escribe tu password."
|
||||
type_in_email: "Ingrese su correo electrónico para confirmar la eliminación de su cuenta."
|
||||
type_in_password: "Asimismo, ingrese su contraseña."
|
||||
email_subscriptions: "Suscripciones de Email"
|
||||
email_subscriptions_none: "No tienes suscripciones."
|
||||
email_announcements: "Noticias"
|
||||
|
@ -789,9 +789,9 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip
|
|||
article_editor_prefix: "¿Ves algún error en nuestros documentos? ¿Quieres hacer algunas instrucciones para tus propias creaciones? Revisa el"
|
||||
article_editor_suffix: "y ayuda a los jugadores de CodeCombat conseguir lo más posible de su tiempo jugando."
|
||||
find_us: "Encuentranos en etsos sitios"
|
||||
social_github: "Checa todo nuestro código en el GitHub"
|
||||
social_github: "Revisa todo nuestro código en GitHub"
|
||||
social_blog: "Lee el blog de CodeCombat en Sett"
|
||||
social_discource: "Unite a la discusión en nuestro foro"
|
||||
social_discource: "Únete a la discusión en nuestro foro"
|
||||
social_facebook: "Me Gusta CodeCombat en Facebook"
|
||||
social_twitter: "Sigue a CodeCombat en Twitter"
|
||||
social_gplus: "Únete a CodeCombat con Google+"
|
||||
|
@ -806,12 +806,12 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip
|
|||
make_private: "Hacer clan privado"
|
||||
subs_only: "solo suscriptores"
|
||||
create_clan: "Crear nuevo clan"
|
||||
private_preview: "Previsualizar"
|
||||
private_preview: "Vista previa"
|
||||
public_clans: "Clanes publicos"
|
||||
my_clans: "Mis Clanes"
|
||||
clan_name: "Nombre del clan"
|
||||
name: "Nombre"
|
||||
chieftain: "Cacique/Líder"
|
||||
chieftain: "Líder del Clan"
|
||||
type: "Tipo"
|
||||
edit_clan_name: "Editar el nombre del Clan"
|
||||
edit_clan_description: "Editar descripción del clan"
|
||||
|
@ -962,7 +962,7 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip
|
|||
indoor: "Interior"
|
||||
desert: "Desierto"
|
||||
grassy: "Herboso"
|
||||
mountain: "Mountaña"
|
||||
mountain: "Montaña"
|
||||
glacier: "Glaciar"
|
||||
small: "Pequeño"
|
||||
large: "Grande"
|
||||
|
@ -985,8 +985,8 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip
|
|||
level_tab_thangs_title: "Tiliches Actuales"
|
||||
level_tab_thangs_all: "Todo"
|
||||
level_tab_thangs_conditions: "Condiciones Iniciales"
|
||||
level_tab_thangs_add: "Agregar Tiliches"
|
||||
level_tab_thangs_search: "Buscar thangs"
|
||||
level_tab_thangs_add: "Agregar Thangs"
|
||||
level_tab_thangs_search: "Buscar Thangs"
|
||||
add_components: "Agregar Componentes"
|
||||
component_configs: "Configuraciones del Componente"
|
||||
config_thang: "Doble clic para configurar un Tiliche"
|
||||
|
@ -1032,8 +1032,8 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip
|
|||
pop_i18n: "Poblar I18N"
|
||||
tasks: "Tareas"
|
||||
clear_storage: "Borrar tus cambios locales"
|
||||
add_system_title: "Agregar Sistemas al Level"
|
||||
done_adding: "Se terminó de agregar"
|
||||
add_system_title: "Agregar Sistemas al Nivel"
|
||||
done_adding: "Finalizar"
|
||||
|
||||
article:
|
||||
edit_btn_preview: "Vista previa"
|
||||
|
@ -1317,7 +1317,7 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip
|
|||
user_polls_record: "Historia de Visitas de Encuestas"
|
||||
|
||||
concepts:
|
||||
advanced_strings: "Manipulación Avanzada de Cadenas"
|
||||
advanced_strings: "Cadenas - Avanzado"
|
||||
algorithms: "Algoritmos"
|
||||
arguments: "Argumentos"
|
||||
arithmetic: "Aritmética"
|
||||
|
@ -1327,18 +1327,17 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip
|
|||
break_statements: "Sentencias Break"
|
||||
classes: "Clases"
|
||||
continue_statements: "Sentencias Continue"
|
||||
for_loops: "Ciclos For"
|
||||
for_loops: "Bucle For"
|
||||
functions: "Funciones"
|
||||
graphics: "Gráficos"
|
||||
if_statements: "Sentencias If"
|
||||
input_handling: "Manejo de Entrada"
|
||||
input_handling: "Manejo de Entradas"
|
||||
math_operations: "Operaciones Matemáticas"
|
||||
object_literals: "Literales de Objeto"
|
||||
parameters: "Parámetros"
|
||||
object_literals: "Objetos Literales"
|
||||
strings: "Cadenas"
|
||||
variables: "Variables"
|
||||
vectors: "Vectores"
|
||||
while_loops: "Ciclos While"
|
||||
while_loops: "Bucles"
|
||||
recursion: "Recursividad"
|
||||
|
||||
delta:
|
||||
|
|
|
@ -26,10 +26,10 @@
|
|||
padding: 2px
|
||||
|
||||
.progress-concept-cell-complete
|
||||
background-color: lightgray
|
||||
background-color: lightgreen
|
||||
|
||||
.progress-concept-cell-started
|
||||
background-color: lightgreen
|
||||
background-color: lightyellow
|
||||
|
||||
.progress-concept-completion-container
|
||||
font-size: 10pt
|
||||
|
@ -76,10 +76,10 @@
|
|||
padding: 2px
|
||||
|
||||
.progress-key-complete
|
||||
background-color: lightgray
|
||||
background-color: lightgreen
|
||||
|
||||
.progress-key-started
|
||||
background-color: lightgreen
|
||||
background-color: lightyellow
|
||||
|
||||
.progress-expand-checkbox
|
||||
margin-left: 14px
|
||||
|
@ -99,11 +99,11 @@
|
|||
|
||||
.progress-level-cell-complete
|
||||
cursor: pointer
|
||||
background-color: lightgray
|
||||
background-color: lightgreen
|
||||
|
||||
.progress-level-cell-started
|
||||
cursor: pointer
|
||||
background-color: lightgreen
|
||||
background-color: lightyellow
|
||||
|
||||
.progess-levels-label
|
||||
color: #317EAC
|
||||
|
|
|
@ -65,7 +65,7 @@ block content
|
|||
a(href="https://plus.google.com/115285980638641924488/posts")
|
||||
img(src="/images/pages/community/logo_g+.png", data-i18n="[data-content]community.social_gplus" data-content="Join CodeCombat on Google+")
|
||||
|
||||
a(href="http://www.hipchat.com/g3plnOKqa")
|
||||
a(href="http://www.hipchat.com/gkaufqwnj")
|
||||
img(src="/images/pages/community/logo_hipchat.png", data-i18n="[data-content]community.social_hipchat" data-content="Chat with us in the public CodeCombat HipChat room")
|
||||
|
||||
.half-width
|
||||
|
|
|
@ -51,7 +51,7 @@ block content
|
|||
| Email us
|
||||
span(data-i18n="contribute.join_desc_3")
|
||||
| , or find us in our
|
||||
a(href="http://www.hipchat.com/g3plnOKqa", data-i18n="contribute.join_url_hipchat") public HipChat room
|
||||
a(href="http://www.hipchat.com/gkaufqwnj", data-i18n="contribute.join_url_hipchat") public HipChat room
|
||||
span
|
||||
span(data-i18n="contribute.join_desc_4")
|
||||
| and we'll go from there!
|
||||
|
|
|
@ -49,7 +49,7 @@ block content
|
|||
li
|
||||
a(href="/editor/level", data-i18n="contribute.artisan_join_step2") Create a new level and explore existing levels.
|
||||
li
|
||||
a(href="http://www.hipchat.com/g3plnOKqa", data-i18n="contribute.artisan_join_step3") Find us in our public HipChat room for help.
|
||||
a(href="http://www.hipchat.com/gkaufqwnj", data-i18n="contribute.artisan_join_step3") Find us in our public HipChat room for help.
|
||||
li
|
||||
a(href="http://discourse.codecombat.com", data-i18n="contribute.artisan_join_step4") Post your levels on the forum for feedback.
|
||||
|
||||
|
|
|
@ -50,21 +50,31 @@ block content
|
|||
|
||||
div.well.well-sm(role='tabpanel')
|
||||
ul.nav.nav-pills(role='tablist')
|
||||
li.active(role='presentation')
|
||||
a(href='#progress', aria-controls='progress', role='tab', data-toggle='tab', data-i18n="courses.progress")
|
||||
if adminMode
|
||||
li.active(role='presentation')
|
||||
a(href='#progress', aria-controls='progress', role='tab', data-toggle='tab', data-i18n="courses.progress")
|
||||
li(role='presentation')
|
||||
a(href='#invite', aria-controls='invite', role='tab', data-toggle='tab', data-i18n="courses.add_students")
|
||||
li(role='presentation')
|
||||
a(href='#levels', aria-controls='levels', role='tab', data-toggle='tab', data-i18n="nav.play")
|
||||
li(role='presentation')
|
||||
a(href='#levels', aria-controls='levels', role='tab', data-toggle='tab', data-i18n="nav.play")
|
||||
else
|
||||
li.active(role='presentation')
|
||||
a(href='#levels', aria-controls='levels', role='tab', data-toggle='tab', data-i18n="nav.play")
|
||||
li(role='presentation')
|
||||
a(href='#progress', aria-controls='progress', role='tab', data-toggle='tab', data-i18n="courses.progress")
|
||||
.tab-content
|
||||
.tab-pane.active#progress(role='tabpanel')
|
||||
+progress-tab
|
||||
if adminMode
|
||||
.tab-pane.active#progress(role='tabpanel')
|
||||
+progress-tab
|
||||
.tab-pane#invite(role='tabpanel')
|
||||
+invite-tab
|
||||
.tab-pane#levels(role='tabpanel')
|
||||
+levels-tab
|
||||
.tab-pane#levels(role='tabpanel')
|
||||
+levels-tab
|
||||
else
|
||||
.tab-pane.active#levels(role='tabpanel')
|
||||
+levels-tab
|
||||
.tab-pane#progress(role='tabpanel')
|
||||
+progress-tab
|
||||
|
||||
mixin progress-tab
|
||||
.container-fluid.progress-summary-container
|
||||
|
@ -141,7 +151,8 @@ mixin progress-members
|
|||
span(style='padding-left:16px;')
|
||||
span.progress-key.progress-key-complete(data-i18n="clans.complete_1")
|
||||
span.progress-key.progress-key-started(data-i18n="clans.started_1")
|
||||
span.progress-key(data-i18n="clans.not_started_1")
|
||||
if showExpandedProgress
|
||||
span.progress-key(data-i18n="clans.not_started_1")
|
||||
input.progress-expand-checkbox(type='checkbox')
|
||||
span.spl.progress-expand-label(data-i18n="courses.expand_details")
|
||||
tbody
|
||||
|
@ -250,19 +261,20 @@ mixin invite-tab
|
|||
h3(data-i18n="courses.invite_link_header")
|
||||
p(data-i18n="courses.invite_link_p_1")
|
||||
.alert.alert-info
|
||||
strong= document.location.origin + "/courses?_ppc=" + view.prepaid.get('code')
|
||||
strong= document.location.origin + "/courses/students?_ppc=" + view.prepaid.get('code')
|
||||
p(data-i18n="courses.invite_link_p_2")
|
||||
.form
|
||||
.form-group
|
||||
textarea#invite-emails-textarea.form-control(
|
||||
rows=3, data-i18n="[placeholder]courses.enter_emails", placeholder="Enter student emails to invite, one per line")
|
||||
rows=3, data-i18n="[placeholder]courses.enter_emails")
|
||||
.help-block(data-i18n="courses.enter_emails")
|
||||
.form-group
|
||||
button#invite-btn.btn.btn-success(data-i18n="courses.send_invites")
|
||||
#invite-emails-sending-alert.alert.alert-info.hide(data-i18n="common.sending")
|
||||
#invite-emails-success-alert.alert.alert-success.hide(data-i18n="play_level.done")
|
||||
|
||||
h3 Class Capacity
|
||||
if view.prepaid.loaded
|
||||
if view.prepaid.loaded && pricePerSeat > 0
|
||||
h3 Class Capacity
|
||||
p
|
||||
span.spr(data-i18n="courses.capacity_used")
|
||||
span #{view.prepaid.get('redeemers').length} / #{view.prepaid.get('maxRedeemers')}.
|
||||
|
@ -277,13 +289,18 @@ mixin levels-tab
|
|||
th(data-i18n="courses.concepts")
|
||||
tbody
|
||||
if campaign
|
||||
- var lastLevelCompleted = true;
|
||||
each level, levelID in campaign.get('levels')
|
||||
tr
|
||||
td
|
||||
button.btn.btn-success.btn-play-level(data-level-slug=level.slug, data-i18n="home.play")
|
||||
if lastLevelCompleted || adminMode
|
||||
button.btn.btn-success.btn-play-level(data-level-slug=level.slug, data-i18n="home.play")
|
||||
td
|
||||
if userLevelStateMap[me.id]
|
||||
div= userLevelStateMap[me.id][levelID]
|
||||
- lastLevelCompleted = userLevelStateMap[me.id][levelID] === 'complete'
|
||||
else
|
||||
- lastLevelCompleted = false
|
||||
td= level.name.replace('Course: ', '')
|
||||
td
|
||||
if levelConceptMap[levelID]
|
||||
|
@ -307,14 +324,5 @@ mixin settings-dialog
|
|||
strong(data-i18n="courses.description")
|
||||
p
|
||||
textarea.settings-description-input(rows=2)= courseInstance.get('description')
|
||||
p(data-i18n="courses.languages_available")
|
||||
p
|
||||
select.form-control.settings-language-select
|
||||
option(value="Python") Python
|
||||
option(value="JavaScript") JavaScript
|
||||
option(value="All Languages", data-i18n="courses.all_lang")
|
||||
p
|
||||
input.settings-public-progress(type='checkbox', checked)
|
||||
span.spl(data-i18n="courses.show_progress")
|
||||
.modal-footer
|
||||
button.btn.btn-save-settings(data-i18n="common.save_changes")
|
||||
|
|
|
@ -37,14 +37,18 @@ block content
|
|||
if courses.length > 1
|
||||
option(value="All Courses", data-i18n="courses.all_courses")
|
||||
|
||||
h3
|
||||
span 2.
|
||||
span.spl(data-i18n="courses.number_students")
|
||||
p(data-i18n="courses.enter_number_students")
|
||||
input.input-seats(type='text', value="#{seats}")
|
||||
if price > 0
|
||||
h3
|
||||
span 2.
|
||||
span.spl(data-i18n="courses.number_students")
|
||||
p(data-i18n="courses.enter_number_students")
|
||||
input.input-seats(type='text', value="#{seats}")
|
||||
|
||||
h3
|
||||
span 3.
|
||||
if price > 0
|
||||
span 3.
|
||||
else
|
||||
span 2.
|
||||
span.spl(data-i18n="courses.name_class")
|
||||
p(data-i18n="courses.displayed_course_page")
|
||||
input.class-name(type='text', placeholder="Mrs. Smith's 4th Period", value="#{className ? className : ''}")
|
||||
|
@ -55,7 +59,7 @@ block content
|
|||
span.spl(data-i18n="courses.buy") Buy
|
||||
else
|
||||
h3
|
||||
span 4.
|
||||
span 3.
|
||||
span.spl(data-i18n="courses.create_class")
|
||||
p
|
||||
if price > 0
|
||||
|
@ -77,11 +81,11 @@ block content
|
|||
+trial-and-questions
|
||||
|
||||
mixin trial-and-questions
|
||||
h3(data-i18n="courses.free_trial")
|
||||
p
|
||||
span.spr(data-i18n="teachers.teacher_subs_1")
|
||||
a(href='/teachers/freetrial', data-i18n="teachers.teacher_subs_2")
|
||||
span.spl(data-i18n="courses.get_access")
|
||||
//- h3(data-i18n="courses.free_trial")
|
||||
//- p
|
||||
//- span.spr(data-i18n="teachers.teacher_subs_1")
|
||||
//- a(href='/teachers/freetrial', data-i18n="teachers.teacher_subs_2")
|
||||
//- span.spl(data-i18n="courses.get_access")
|
||||
|
||||
h3(data-i18n="courses.questions")
|
||||
p
|
||||
|
|
|
@ -9,6 +9,8 @@ block content
|
|||
br
|
||||
if state === 'enrolling'
|
||||
.alert.alert-info Enrolling in course..
|
||||
else if state === 'ppc_logged_out'
|
||||
.alert.alert-success Log in or create an account to join this course.
|
||||
else
|
||||
if state === 'unknown_error'
|
||||
.alert.alert-danger.alert-dismissible= stateMessage
|
||||
|
@ -55,6 +57,14 @@ mixin teacher-main
|
|||
.well.well-sm
|
||||
div.praise-quote "#{praise.quote}"
|
||||
div.praise-caption - #{praise.source}
|
||||
|
||||
//- h1.center(data-i18n="courses.free_trial")
|
||||
//- .info-container
|
||||
//- p
|
||||
//- span.spr(data-i18n="teachers.teacher_subs_1")
|
||||
//- a(href='/teachers/freetrial', data-i18n="teachers.teacher_subs_2")
|
||||
//- span.spl(data-i18n="courses.get_access")
|
||||
|
||||
h2.center(data-i18n="courses.choose_course")
|
||||
|
||||
mixin student-dialog(course)
|
||||
|
|
|
@ -106,7 +106,7 @@ block header
|
|||
li
|
||||
a(href='https://github.com/codecombat/codecombat/wiki/Artisan-Home', data-i18n="editor.wiki", target="_blank") Wiki
|
||||
li
|
||||
a(href='http://www.hipchat.com/g3plnOKqa', data-i18n="editor.live_chat", target="_blank") Live Chat
|
||||
a(href='http://www.hipchat.com/gkaufqwnj', data-i18n="editor.live_chat", target="_blank") Live Chat
|
||||
li
|
||||
a(href='http://discourse.codecombat.com/category/artisan', data-i18n="nav.forum", target="_blank") Forum
|
||||
li
|
||||
|
|
|
@ -67,7 +67,7 @@ block header
|
|||
li
|
||||
a(href='https://github.com/codecombat/codecombat/wiki/Artisan-Home', data-i18n="editor.wiki", target="_blank") Wiki
|
||||
li
|
||||
a(href='http://www.hipchat.com/g3plnOKqa', data-i18n="editor.live_chat", target="_blank") Live Chat
|
||||
a(href='http://www.hipchat.com/gkaufqwnj', data-i18n="editor.live_chat", target="_blank") Live Chat
|
||||
li
|
||||
a(href='http://discourse.codecombat.com/category/artisan', data-i18n="nav.forum", target="_blank") Forum
|
||||
li
|
||||
|
|
|
@ -43,7 +43,7 @@ block header
|
|||
li
|
||||
a(href='https://github.com/codecombat/codecombat/wiki', data-i18n="editor.wiki", target="_blank") Wiki
|
||||
li
|
||||
a(href='http://www.hipchat.com/g3plnOKqa', data-i18n="editor.live_chat", target="_blank") Live Chat
|
||||
a(href='http://www.hipchat.com/gkaufqwnj', data-i18n="editor.live_chat", target="_blank") Live Chat
|
||||
li
|
||||
a(href='http://discourse.codecombat.com/category/diplomat', data-i18n="nav.forum", target="_blank") Forum
|
||||
li
|
||||
|
|
|
@ -54,6 +54,7 @@ module.exports = class CourseDetailsView extends RootView
|
|||
context.memberUserMap = @memberUserMap ? {}
|
||||
context.noCourseInstance = @noCourseInstance
|
||||
context.noCourseInstanceSelected = @noCourseInstanceSelected
|
||||
context.pricePerSeat = @course.get('pricePerSeat')
|
||||
context.showExpandedProgress = @showExpandedProgress
|
||||
context.sortedMembers = @sortedMembers ? []
|
||||
context.userConceptStateMap = @userConceptStateMap ? {}
|
||||
|
@ -150,11 +151,11 @@ module.exports = class CourseDetailsView extends RootView
|
|||
levelStateMap[levelID] = state
|
||||
|
||||
@instanceStats.totalLevelsCompleted++ if state is 'complete'
|
||||
@instanceStats.totalPlayTime += levelSession.get('playtime')
|
||||
@instanceStats.totalPlayTime += parseInt(levelSession.get('playtime') ? 0)
|
||||
|
||||
@memberStats[userID] ?= totalLevelsCompleted: 0, totalPlayTime: 0
|
||||
@memberStats[userID].totalLevelsCompleted++ if state is 'complete'
|
||||
@memberStats[userID].totalPlayTime += levelSession.get('playtime')
|
||||
@memberStats[userID].totalPlayTime += parseInt(levelSession.get('playtime') ? 0)
|
||||
|
||||
@userConceptStateMap[userID] ?= {}
|
||||
for concept of @levelConceptMap[levelID]
|
||||
|
@ -168,6 +169,7 @@ module.exports = class CourseDetailsView extends RootView
|
|||
|
||||
if @courseInstance.get('members').length > 0
|
||||
@instanceStats.averageLevelsCompleted = @instanceStats.totalLevelsCompleted / @courseInstance.get('members').length
|
||||
@instanceStats.averageLevelPlaytime = @instanceStats.totalPlayTime / @courseInstance.get('members').length
|
||||
for levelID, level of @campaign.get('levels')
|
||||
@instanceStats.furthestLevelCompleted = level.name if levelStateMap[levelID] is 'complete'
|
||||
|
||||
|
|
|
@ -58,15 +58,16 @@ module.exports = class CourseEnrollView extends RootView
|
|||
onClickBuy: (e) ->
|
||||
return @openModalView new AuthModal() if me.isAnonymous()
|
||||
|
||||
if @seats < 1 or not _.isFinite(@seats)
|
||||
alert("Please enter the maximum number of students needed for your class.")
|
||||
return
|
||||
|
||||
if @price is 0
|
||||
@seats = 9999
|
||||
@state = 'creating'
|
||||
@createClass()
|
||||
return
|
||||
|
||||
if @seats < 1 or not _.isFinite(@seats)
|
||||
alert("Please enter the maximum number of students needed for your class.")
|
||||
return
|
||||
|
||||
@state = undefined
|
||||
@stateMessage = undefined
|
||||
@render()
|
||||
|
|
|
@ -20,13 +20,18 @@ module.exports = class CoursesView extends RootView
|
|||
constructor: (options) ->
|
||||
super(options)
|
||||
@praise = utils.getCoursePraise()
|
||||
@studentMode = utils.getQueryVariable('student', false) or options.studentMode
|
||||
@studentMode = Backbone.history.getFragment()?.indexOf('courses/students') >= 0
|
||||
@courses = new CocoCollection([], { url: "/db/course", model: Course})
|
||||
@supermodel.loadCollection(@courses, 'courses')
|
||||
@courseInstances = new CocoCollection([], { url: "/db/user/#{me.id}/course_instances", model: CourseInstance})
|
||||
@listenToOnce @courseInstances, 'sync', @onCourseInstancesLoaded
|
||||
@supermodel.loadCollection(@courseInstances, 'course_instances')
|
||||
@courseEnroll(prepaidCode) if prepaidCode = utils.getQueryVariable('_ppc', false)
|
||||
if prepaidCode = utils.getQueryVariable('_ppc', false)
|
||||
if me.isAnonymous()
|
||||
@state = 'ppc_logged_out'
|
||||
else
|
||||
@studentMode = true
|
||||
@courseEnroll(prepaidCode)
|
||||
|
||||
getRenderData: ->
|
||||
context = super()
|
||||
|
@ -75,7 +80,7 @@ module.exports = class CoursesView extends RootView
|
|||
onClickEnroll: (e) ->
|
||||
$('.continue-dialog').modal('hide')
|
||||
courseID = $(e.target).data('course-id')
|
||||
prepaidCode = $(".code-input[data-course-id=#{courseID}]").val()
|
||||
prepaidCode = ($(".code-input[data-course-id=#{courseID}]").val() ? '').trim()
|
||||
@courseEnroll(prepaidCode)
|
||||
|
||||
onClickEnter: (e) ->
|
||||
|
@ -89,17 +94,15 @@ module.exports = class CoursesView extends RootView
|
|||
Backbone.Mediator.publish 'router:navigate', navigationEvent
|
||||
|
||||
onClickStudent: (e) ->
|
||||
route = "/courses?student=true"
|
||||
route = "/courses/students"
|
||||
viewClass = require 'views/courses/CoursesView'
|
||||
viewArgs = [studentMode: true]
|
||||
navigationEvent = route: route, viewClass: viewClass, viewArgs: viewArgs
|
||||
navigationEvent = route: route, viewClass: viewClass, viewArgs: []
|
||||
Backbone.Mediator.publish 'router:navigate', navigationEvent
|
||||
|
||||
onClickTeacher: (e) ->
|
||||
route = "/courses?student=false"
|
||||
route = "/courses/teachers"
|
||||
viewClass = require 'views/courses/CoursesView'
|
||||
viewArgs = [studentMode: false]
|
||||
navigationEvent = route: route, viewClass: viewClass, viewArgs: viewArgs
|
||||
navigationEvent = route: route, viewClass: viewClass, viewArgs: []
|
||||
Backbone.Mediator.publish 'router:navigate', navigationEvent
|
||||
|
||||
courseEnroll: (prepaidCode) ->
|
||||
|
|
|
@ -51,7 +51,7 @@ function generateNewCode(done)
|
|||
function createCode(length)
|
||||
{
|
||||
var text = "";
|
||||
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
var possible = "abcdefghijklmnopqrstuvwxyz0123456789";
|
||||
|
||||
for( var i=0; i < length; i++ )
|
||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
async = require 'async'
|
||||
Handler = require '../commons/Handler'
|
||||
Campaign = require '../campaigns/Campaign'
|
||||
Course = require './Course'
|
||||
CourseInstance = require './CourseInstance'
|
||||
LevelSession = require '../levels/sessions/LevelSession'
|
||||
|
@ -88,11 +89,19 @@ CourseInstanceHandler = class CourseInstanceHandler extends Handler
|
|||
CourseInstance.findById courseInstanceID, (err, courseInstance) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
return @sendNotFoundError(res) unless courseInstance
|
||||
memberIDs = _.map courseInstance.get('members') ? [], (memberID) -> memberID.toHexString?() or memberID
|
||||
LevelSession.find {creator: {$in: memberIDs}}, (err, documents) =>
|
||||
return @sendDatabaseError(res, err) if err?
|
||||
cleandocs = (LevelSessionHandler.formatEntity(req, doc) for doc in documents)
|
||||
@sendSuccess(res, cleandocs)
|
||||
Course.findById courseInstance.get('courseID'), (err, course) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
return @sendNotFoundError(res) unless course
|
||||
Campaign.findById course.get('campaignID'), (err, campaign) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
return @sendNotFoundError(res) unless campaign
|
||||
levelIDs = (levelID for levelID of campaign.get('levels'))
|
||||
memberIDs = _.map courseInstance.get('members') ? [], (memberID) -> memberID.toHexString?() or memberID
|
||||
query = {$and: [{creator: {$in: memberIDs}}, {'level.original': {$in: levelIDs}}]}
|
||||
LevelSession.find query, (err, documents) =>
|
||||
return @sendDatabaseError(res, err) if err?
|
||||
cleandocs = (LevelSessionHandler.formatEntity(req, doc) for doc in documents)
|
||||
@sendSuccess(res, cleandocs)
|
||||
|
||||
getMembersAPI: (req, res, courseInstanceID) ->
|
||||
CourseInstance.findById courseInstanceID, (err, courseInstance) =>
|
||||
|
@ -112,20 +121,25 @@ CourseInstanceHandler = class CourseInstanceHandler extends Handler
|
|||
return @sendNotFoundError(res) unless courseInstance
|
||||
return @sendForbiddenError(res) unless @hasAccessToDocument(req, courseInstance)
|
||||
|
||||
Prepaid.findById courseInstance.get('prepaidID'), (err, prepaid) =>
|
||||
Course.findById courseInstance.get('courseID'), (err, course) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
return @sendNotFoundError(res) unless prepaid
|
||||
return @sendForbiddenError(res) unless prepaid.get('maxRedeemers') > prepaid.get('redeemers').length
|
||||
for email in req.body.emails
|
||||
context =
|
||||
email_id: sendwithus.templates.course_invite_email
|
||||
recipient:
|
||||
address: email
|
||||
email_data:
|
||||
class_name: courseInstance.get('name')
|
||||
join_link: "https://codecombat.com/courses?_ppc=" + prepaid.get('code')
|
||||
sendwithus.api.send context, _.noop
|
||||
return @sendSuccess(res, {})
|
||||
return @sendNotFoundError(res) unless course
|
||||
|
||||
Prepaid.findById courseInstance.get('prepaidID'), (err, prepaid) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
return @sendNotFoundError(res) unless prepaid
|
||||
return @sendForbiddenError(res) unless prepaid.get('maxRedeemers') > prepaid.get('redeemers').length
|
||||
for email in req.body.emails
|
||||
context =
|
||||
email_id: sendwithus.templates.course_invite_email
|
||||
recipient:
|
||||
address: email
|
||||
subject: course.get('name')
|
||||
email_data:
|
||||
class_name: course.get('name')
|
||||
join_link: "https://codecombat.com/courses/students?_ppc=" + prepaid.get('code')
|
||||
sendwithus.api.send context, _.noop
|
||||
return @sendSuccess(res, {})
|
||||
|
||||
redeemPrepaidCodeAPI: (req, res) ->
|
||||
return @sendUnauthorizedError(res) if not req.user? or req.user?.isAnonymous()
|
||||
|
@ -142,6 +156,9 @@ CourseInstanceHandler = class CourseInstanceHandler extends Handler
|
|||
return @sendDatabaseError(res, err) if err
|
||||
return @sendForbiddenError(res) if prepaid.get('redeemers')?.length >= prepaid.get('maxRedeemers')
|
||||
|
||||
if _.find((prepaid.get('redeemers') ? []), (a) -> a.userID.equals(req.user.id))
|
||||
return @sendSuccess(res, courseInstances)
|
||||
|
||||
# Add to prepaid redeemers
|
||||
query =
|
||||
_id: prepaid.get('_id')
|
||||
|
|
|
@ -6,7 +6,7 @@ PrepaidSchema.index({code: 1}, { unique: true })
|
|||
|
||||
PrepaidSchema.statics.generateNewCode = (done) ->
|
||||
tryCode = ->
|
||||
code = _.sample("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 8).join('')
|
||||
code = _.sample("abcdefghijklmnopqrstuvwxyz0123456789", 8).join('')
|
||||
Prepaid.findOne code: code, (err, prepaid) ->
|
||||
return done() if err
|
||||
return done(code) unless prepaid
|
||||
|
|
|
@ -222,7 +222,7 @@ describe 'CourseInstance', ->
|
|||
|
||||
describe 'Redeem prepaid code', ->
|
||||
|
||||
it 'Redeem prepaid code an instance of max 2', (done) ->
|
||||
it 'Redeem prepaid code for an instance of max 2', (done) ->
|
||||
stripe.tokens.create {
|
||||
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
|
||||
}, (err, token) ->
|
||||
|
@ -264,7 +264,7 @@ describe 'CourseInstance', ->
|
|||
expect(usersFound).toEqual(2)
|
||||
done()
|
||||
|
||||
it 'Redeem full prepaid code on instance of max 1', (done) ->
|
||||
it 'Redeem full prepaid code for on instance of max 1', (done) ->
|
||||
stripe.tokens.create {
|
||||
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
|
||||
}, (err, token) ->
|
||||
|
@ -326,3 +326,29 @@ describe 'CourseInstance', ->
|
|||
return done(err) if err
|
||||
expect(prepaid.get('redeemers')?.length).toEqual(prepaid.get('maxRedeemers'))
|
||||
done()
|
||||
|
||||
it 'Redeem prepaid code twice', (done) ->
|
||||
stripe.tokens.create {
|
||||
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
|
||||
}, (err, token) ->
|
||||
loginNewUser (user1) ->
|
||||
createCourse 0, (err, course) ->
|
||||
expect(err).toBeNull()
|
||||
return done(err) if err
|
||||
createCourseInstances user1, course.get('_id'), 2, token.id, (err, courseInstances) ->
|
||||
expect(err).toBeNull()
|
||||
return done(err) if err
|
||||
expect(courseInstances.length).toEqual(1)
|
||||
Prepaid.findById courseInstances[0].get('prepaidID'), (err, prepaid) ->
|
||||
expect(err).toBeNull()
|
||||
return done(err) if err
|
||||
loginNewUser (user2) ->
|
||||
# Redeem once
|
||||
request.post {uri: courseInstanceRedeemURL, json: {prepaidCode: prepaid.get('code')} }, (err, res) ->
|
||||
expect(err).toBeNull()
|
||||
expect(res.statusCode).toBe(200)
|
||||
# Redeem twice
|
||||
request.post {uri: courseInstanceRedeemURL, json: {prepaidCode: prepaid.get('code')} }, (err, res) ->
|
||||
expect(err).toBeNull()
|
||||
expect(res.statusCode).toBe(200)
|
||||
done()
|
||||
|
|
Loading…
Add table
Reference in a new issue