Updated es-419.coffee Spanish translations

--- app/locale/es-419.coffee
+++ app/locale/es-419.coffee
@@ -25,14 +25,14 @@
     global_subtitle3: "Maestros"
     global_subtitle4: "Países"
     sign_up: "Registrarse"
-#    learner_number: "12 Million"
-#    code_line_number: "1 Billion"
+    learner_number: "12 millones"
+    code_line_number: "1000 millones"

   new_home:
     title: "CodeCombat - Juegos de Programación para aprender Python y JavaScript" # {change}
-#    meta_keywords: "CodeCombat, python, javascript, Coding Games, Computer Science Program, Adventure, Epic Story"
+    meta_keywords: "CodeCombat, Python, JavaScript, juegos de programación, programa de Ciencias de la Computación, aventura, historia épica"
     meta_description: "Aprende a esciribir código a través de un juego de programación. Aprende Python, JavaScript y HTML mientras resuelves rompecabezas y aprendes a hacer tus propios juegos de código y páginas web." # {change}
-#    become_investor: "to become an investor in CodeCombat"
+    become_investor: "para invertir en CodeCombat"
     built_for_teachers_title: "Un Juego de programación construido teniendo en cuenta a los maestros."
     built_for_teachers_blurb: "Enseñar a los niños a programar puede ser abrumador. CodeCombat ayuda a todos los educadores a enseñar a los estudiantes cómo programar en JavaScript o Python, dos de los lenguajes de programación más populares. Con un plan de estudios comprensivo que incluye seis unidades de Ciencias de la Computación, y refuerza el aprendizaje a través de unidades en el desarrollo de juegos y desarrollo web basados en proyectos, los niños progresarán en un viaje desde la sintaxis básica hasta la recursión!"
     built_for_teachers_subtitle1: "Ciencias de la Computación"
@@ -59,7 +59,7 @@
     classroom_in_box_blurb1: "Involucre a todos los estudiantes para que confíen que la programación es para ellos."
     classroom_in_box_blurb2: "Apoderar a cualquier educador para que se sienta seguro al enseñar programación."
     classroom_in_box_blurb3: "Inspirar a todos los líderes escolares para crear un programa de Ciencias de la Computación de clase mundial."
-#    click_here: "Click here"
+    click_here: "Haz clic aquí"
     creativity_rigor_title: "Donde la creatividad alcanza el rigor"
     creativity_rigor_subtitle1: "Haga que la programación sea divertida y enseñe habilidades del mundo real"
     creativity_rigor_blurb1: "Los estudiantes escriben Python y JavaScript real mientras juegan juegos que fomentan el ensayo y error, el pensamiento crítico y la creatividad. Luego, los estudiantes aplican las habilidades de programación que han aprendido al desarrollar sus propios juegos y sitios web en cursos basados en proyectos."
@@ -116,7 +116,7 @@
     teachers_love_codecombat_blurb2: "Recomendarían CodeCombat a otros maestros de Ciencias de la Computación"
     teachers_love_codecombat_blurb3: "Dicen que CodeCombat los ayuda a mejorar las habilidades de resolución de problemas de sus estudiantes"
     teachers_love_codecombat_subblurb: "En asociación con McREL International, líder en orientación basada en investigación y evaluaciones de tecnología educativa."
-#    top_banner_blurb: "New: join our mission!"
+    top_banner_blurb: "Nuevo: ¡Únete a nuestra misión!"
     try_the_game: "Prueba el juego"
     classroom_edition: "Versión para el aula:"
     learn_to_code: "Aprende a programar:"
@@ -183,7 +183,7 @@
     want_coco: "¿Quisiera CodeCombat en su escuela?"
     educator: "Maestro"
     student: "Estudiante"
-#    go_to_my_dashboard: "Go to My Dashboard"
+    go_to_my_dashboard: "Ir a mi panel"

   nav:
     educators: "Maestros"
@@ -231,13 +231,13 @@
     faq: "Preguntas frecuentes"
     return_coco: "Volver a CodeCombat"
     classes: "Clases"
-#    term_of_service: "Terms of Service"
+    term_of_service: "Términos de Servicio"

   modal:
     close: "Cerrar"
     okay: "OK"
     cancel: "Cancelar"
-#    try_again: "Try Again"
+    try_again: "Volver a intentarlo"

   not_found:
     page_not_found: "Página no encontrada"
@@ -307,7 +307,7 @@
     back_to_dashboard: "Volver al panel"
     back_to_ozaria: "Volver a ozaria.com"
     customize_hero: "Personalizar héroe"
-#    account_settings: "Account Settings"
+    account_settings: "Configuración de cuenta"

   code:
     if: "si" # Keywords--these translations show up on hover, so please translate them all, even if it's kind of long. (In the code editor, they will still be in English.)
@@ -384,8 +384,8 @@
     already_have_account1: "¿Ya tienes una cuenta de maestro o estudiante de CodeCombat?"
     already_have_account2: "Puedes ingresar a Ozaria.com con esa cuenta."
     sign_into_ozaria: "Ingresa a Ozaria.com"
-#    individual_users_not_supported: "Ozaria is for classrooms only!  Please visit CodeCombat to continue playing!"
-#    accounts_merge_confirmation: "There is an account associated with the email from this Google account. Would you like to merge these accounts?"
+    individual_users_not_supported: "¡Ozaria es solo para aulas!  ¡Visita CodeCombat para seguir jugando!"
+    accounts_merge_confirmation: "Hay una cuenta asociada al correo electrónico de esta cuenta de Google. ¿Quieres combinar las cuentas?"

   signup:
     complete_subscription: "Completa Suscripción"
@@ -471,24 +471,24 @@
     request_class_code: "¡Estudiantes, pídanle un código de clase a su docente para crear una cuenta!"
     educators_create_account: "¡Docentes, creen una cuenta de maestro para acceder a la primera unidad de manera gratuita!"
     looking_for_classroom: "Buscando aulas..."
-#    create_your_educator_account: "Create your educator account"
-#    educator_signup_list_1_title: "See what your students see:"
-#    educator_signup_list_1_desc: "Demo each game level in the first unit"
-#    educator_signup_list_2_title: "See teacher resources:"
-#    educator_signup_list_2_desc: "Access scope and sequence, lessons, and more"
-#    educator_signup_list_3_title: "See your students thrive:"
-#    educator_signup_list_3_desc: "Try the first unit with your class for free"
-#    signup_with_email: "Sign up with Email"
-#    already_have_account: "Already have a CodeCombat or Ozaria account"
-#    select_your_role: "Select your role"
-#    select_your_state: "Select your state"
-
-#  form_validation_errors:
-#    required: "Field is required"
-#    invalidEmail: "Invalid email"
-#    invalidPhone: "Invalid phone number"
-#    emailExists: "Email already exists"
-#    numberGreaterThanZero: "Should be a number greater than 0"
+    create_your_educator_account: "Crea una cuenta de educador"
+    educator_signup_list_1_title: "Ve lo mismo que tus estudiantes:"
+    educator_signup_list_1_desc: "Prueba cada nivel del juego en la primera unidad"
+    educator_signup_list_2_title: "Ve los recursos para maestros:"
+    educator_signup_list_2_desc: "Accede al alcance y el orden, las lecciones y más"
+    educator_signup_list_3_title: "Observa el progreso de tus estudiantes:"
+    educator_signup_list_3_desc: "Prueba gratis la primera unidad con tu clase"
+    signup_with_email: "Regístrate con tu correo electrónico"
+    already_have_account: "Ya tienes una cuenta de Ozaria o CodeCombat"
+    select_your_role: "Selecciona tu rol"
+    select_your_state: "Selecciona tu estado"
+
+  form_validation_errors:
+    required: "El campo es obligatorio"
+    invalidEmail: "Correo no válido"
+    invalidPhone: "Número de teléfono no válido"
+    emailExists: "El correo electrónico ya existe"
+    numberGreaterThanZero: "Debe ser un número mayor que 0"

   recover:
     recover_account_title: "recuperar cuenta"
@@ -544,9 +544,9 @@
     complete: "Completado"
     continue_editing: "Continuar editando"
     logout: "Cerrar sesión"
-#    empty: "Empty"
-#    done: "Done"
-#    remove: "Remove"
+    empty: "Vacío"
+    done: "Listo"
+    remove: "Quitar"

   general:
     and: "y"
@@ -609,7 +609,7 @@
     more: "Más"
     fewer: "Menos"
     with: "con"
-#    archived: "Archived"
+    archived: "Archivado"

   units:
     second: "segundo"
@@ -815,31 +815,31 @@
     learning_goals: "Metas de aprendizaje"
     start: "Empezar"
     vega_character: "Personaje Vega"
-#    capella_character: "Capella Character"
-#    octans_character: "Octans Character"
+    capella_character: "Personaje Capella"
+    octans_character: "Personaje Octans"
     click_to_continue: "Haz clic para continuar"
     share_your_project: "Comparte tu proyecto"
     keep_editing_your_project: "Siempre puedes seguir editando tu proyecto, incluso después compartirlo."
     copy_url: "Copiar URL"
-#    level_status: "Status"
-#    level_status_locked: "Locked"
-#    level_status_in_progress: "In Progress"
-#    level_status_complete: "Complete"
-#    level_type_practice: "Practice"
-#    level_type_challenge: "Challenge"
-#    level_type_cutscene: "Cutscene"
-#    level_type_capstone: "Capstone"
-#    level_type_intro: "Intro"
-#    level_type_practice_level: "Practice Level"
-#    level_type_challenge_level: "Challenge Level"
-#    level_type_cutscene_level: "Cutscene Level"
-#    level_type_capstone_level: "Capstone Level"
-#    level_type_capstone_project: "Capstone Project"
-#    level_type_intro_level: "Intro Level"
-#    content_type_avatar: "Avatar Selection"
-#    content_type_cinematic: "Cinematic"
-#    content_type_interactive: "Concept Check"
-#    feedback: "Feedback"
+    level_status: "Estado"
+    level_status_locked: "Bloqueado"
+    level_status_in_progress: "En curso"
+    level_status_complete: "Completado"
+    level_type_practice: "Práctica"
+    level_type_challenge: "Desafío"
+    level_type_cutscene: "Escena"
+    level_type_capstone: "Final"
+    level_type_intro: "Introducción"
+    level_type_practice_level: "Nivel de práctica"
+    level_type_challenge_level: "Nivel de desafío"
+    level_type_cutscene_level: "Nivel de escena"
+    level_type_capstone_level: "Nivel final"
+    level_type_capstone_project: "Proyecto final"
+    level_type_intro_level: "Nivel introductorio"
+    content_type_avatar: "Selección de avatar"
+    content_type_cinematic: "Cinemática"
+    content_type_interactive: "Comprobación de conceptos"
+    feedback: "Comentarios"

   apis:
     methods: "Métodos"
@@ -1146,7 +1146,7 @@
     optional_parameters: "Parámetros Opcionales"
     returns: "Devoluciones"
     granted_by: "Concedido por"
-#    description_coming_soon: "Description coming soon"
+    description_coming_soon: "Descripción próximamente"

   save_load:
     granularity_saved_games: "Guardado"
@@ -1187,22 +1187,22 @@
     nick_title: "Cofundador, CEO"
     matt_title: "Cofundador, CTO"
     lawrence_title: "Gerente de relaciones comerciales"
-    shan_title: "Encargado principal de establecimiento de asociaciones, China"
+    shan_title: "Encargada principal de establecimiento de asociaciones, China"
     run_title: "Jefe de operaciones, China"
     lance_title: "Pasante de ingeniería de software, China"
-#    zhiran_title: "Head of Curriculum, CodeCombat Greater China"
-#    yuqiang_title: "Head of Innovation, CodeCombat Greater China"
+    zhiran_title: "Jefa de planes de estudio, China"
+    yuqiang_title: "Jefe de innovación, China"
     matias_title: "Ingeniero de software senior"
     ryan_title: "Especialista en atención al cliente"
-    maya_title: "Desarrollador de planes de estudio senior"
+    maya_title: "Desarrolladora de planes de estudio senior"
     bill_title: "Gerente general, China"
-    shasha_title: "Diseñador visual y de productos"
+    shasha_title: "Diseñadora visual y de productos"
     daniela_title: "Gerente de marketing"
     bobby_title: "Diseñador de juegos"
     brian_title: "Diseñador de juegos principal" # {change}
     stephanie_title: "Especialista en atención al cliente"
     rob_title: "Representante de desarrollo de ventas"
-    shubhangi_title: "Ingeniero de software senior"
+    shubhangi_title: "Ingeniera de software senior"
     retrostyle_title: "Ilustración"
     retrostyle_blurb: "Juegos con estilo Retro"
     community_title: "...y nuestra comunidad open-source"
@@ -1255,43 +1255,43 @@

   teachers:
     licenses_needed: "Licencias necesarias"
-#    google_classroom_disabled_text: "To link Google Classroom, login using Google SSO"
-#    select_class: "Select class"
-#    programming_language: "Programming language"
-#    programming_language_edit_desc: "Language cannot be changed once students join a class. We recommend creating a new class for the same students."
-#    class_name: "Class Name"
-#    grades: "Grades"
-#    select_all_that_apply: "Select all that apply"
-#    class_info_modal_sub_title: "Invite students to join your class by sending them:"
-#    class_url: "Class Url"
-#    class_url_desc: "Share URL to have students join your class after signing up"
-#    class_code: "Class Code"
-#    class_code_desc: "Students can enter this code when they sign up at www.ozaria.com"
-#    invite_by_email: "Invite by Email"
-#    invite_modal_sub_title: "Separate each email address by a line break or commas"
-#    no_new_students_imported: "No new students imported"
-#    error_in_importing_classrooms: "Error in importing classrooms"
-#    error_in_importing_students: "Error in importing students"
-
-#  no_licenses_page:
-#    are_your_students_ready: "Are your students excited and ready to learn more?"
-#    once_students_have_completed: "Once students have completed Chapter 1, they will need licenses in order to continue their journey through the next chapter and beyond."
-#    get_licenses: "Get Licenses"
-#    get_full_access_to_ozaria: "Get Full Access to Ozaria"
-#    unlock_all: "Unlock all the above plus all the teacher support you need"
-#    comprehensive_dashboards: "Comprehensive teacher and administrator dashboards"
-#    lesson_plans_rubrics_guides: "Lesson Plans, Project Rubrics, Solution Guides, and more"
-#    dedicated_customer_success_manager: "A dedicated customer success manager"
-#    automatic_access: "Automatic access to new content and features"
-#    license_quote: "\"In my opinion as a 26 year veteran middle school educator, there is no more engaging resource for teaching coding.\""
-#    join_our_community: "Join our community"
-#    join_our_community_desc: "Over 12 million people have learned to write Python and JavaScript using CodeCombat. Now, we welcome you to join our community of over 82,000 educators and 25,000 schools with Ozaria, designed for educators who want to inspire a new generation of students to learn Computer Science."
-#    teach_real_typed_code: "Teach Real, Typed Code"
-#    teach_real_typed_code_desc: "Students learn Python and JavaScript, the two most popular coding languages, and build 21st century skills that will prepare them for any career."
-#    support_student_progress: "Support Student Progress"
-#    support_student_progress_desc: "Unlock formative and summative assessment data from Concept Checks, Levels, and Capstone Projects to support student progress."
-#    creative_projects: "Creative Projects"
-#    creative_projects_desc: "Inspire imagination and unleash student creativity with shareable capstone projects, like in Chapter 2 where students design their own storytelling game. "
+    google_classroom_disabled_text: "Para vincular Google Classroom, inicia sesión con el SSO de Google"
+    select_class: "Selecciona una clase"
+    programming_language: "Lenguaje de programación"
+    programming_language_edit_desc: "No es posible cambiar el lenguaje una vez que ya hay estudiantes en la clase. Recomendamos crear una nueva clase para los mismos estudiantes."
+    class_name: "Nombre de la clase"
+    grades: "Grados"
+    select_all_that_apply: "Selecciona las opciones que correspondan"
+    class_info_modal_sub_title: "Para invitar a los estudiantes a que se unan a tu clase, envíales lo siguiente:"
+    class_url: "Enlace de la clase"
+    class_url_desc: "Comparte el enlace para que los estudiantes se unan a tu clase tras registrarse"
+    class_code: "Código de la clase"
+    class_code_desc: "Los estudiantes pueden ingresar este código al registrarse en www.ozaria.com"
+    invite_by_email: "Invitación por correo electrónico"
+    invite_modal_sub_title: "Separa cada dirección de correo con un salto de línea o una coma"
+    no_new_students_imported: "No se importó ningún estudiante nuevo"
+    error_in_importing_classrooms: "Error al importar aulas"
+    error_in_importing_students: "Error al importar estudiantes"
+
+  no_licenses_page:
+    are_your_students_ready: "¿Tus estudiantes están entusiasmados por aprender más?"
+    once_students_have_completed: "Una vez que los estudiantes hayan completado el capítulo 1, necesitarán licencias para continuar aprendiendo con los próximos capítulos."
+    get_licenses: "Conseguir licencias"
+    get_full_access_to_ozaria: "Obtener acceso completo a Ozaria"
+    unlock_all: "Desbloquea todo lo anterior y recibe apoyo para maestros"
+    comprehensive_dashboards: "Paneles muy completos para maestros y administradores"
+    lesson_plans_rubrics_guides: "Planes de estudios, guías de evaluación, guías de soluciones y más"
+    dedicated_customer_success_manager: "Un gerente exclusivo de satisfacción del cliente"
+    automatic_access: "Acceso automático a nuevas funciones y contenido"
+    license_quote: "\"En mi opinión, como maestro veterano de secundaria con 26 años de experiencia, no existe mejor recurso para enseñar programación\"."
+    join_our_community: "Únete a nuestra comunidad"
+    join_our_community_desc: "Más de 12 millones de personas han aprendido a escribir en Python y JavaScript con CodeCombat. Ahora te invitamos a unirte a nuestra comunidad de más de 82 000 educadores y 25 000 escuelas que usan Ozaria, un programa diseñado para docentes que quieren inspirar a la nueva generación de estudiantes a aprender Ciencias de la Computación."
+    teach_real_typed_code: "Enseña código real y práctico"
+    teach_real_typed_code_desc: "Los estudiantes aprenden Python y JavaScript, los dos lenguajes de programación más populares, y desarrollan habilidades del siglo XXI que les servirán para cualquier carrera."
+    support_student_progress: "Ayuda a los estudiantes a progresar"
+    support_student_progress_desc: "Obtén datos de evaluaciones formativas y sumativas a partir de comprobaciones de conceptos, niveles y proyectos finales para ayudar a los estudiantes a progresar."
+    creative_projects: "Proyectos creativos"
+    creative_projects_desc: "Despierta la imaginación y desata la creatividad de tus estudiantes con los proyectos finales. Por ejemplo, en el capítulo 2, deben diseñar su propio juego narrativo, que luego pueden compartir con quien quieran. "

   special_offer:
     special_offer: "Oferta Especial"
@@ -1343,7 +1343,7 @@
     role_default: "Seleccione Rol"
     primary_role_default: "Seleccione Rol Primario"
     purchaser_role_default: "Seleccione Rol de Comprador"
-#    teacher: "Teacher"
+    teacher: "Maestro"
     tech_coordinator: "Coordinador de tecnología"
     advisor: "Tutor" # {change}
     principal: "Director"
@@ -1489,7 +1489,7 @@

   cinematic:
     click_anywhere_continue: "haz clic en cualquier lado para continuar" # {change}
-#    instructional_tooltip: "Click these arrow buttons to go backwards and forwards. You can also use the arrow keys on your keyboard to do the same."
+    instructional_tooltip: "Haz clic en los botones de flechas para avanzar o retroceder. También puedes usar las teclas de dirección del teclado."

   community:
     main_title: "Comunidad CodeCombat"
@@ -1803,7 +1803,7 @@
     solution_arena_blurb: "Se alienta a los estudiantes a solucionar los niveles de arena con creatividad. La siguiente solución cumple con los requisitos del nivel."
     solution_challenge_blurb: "Se alienta a los estudiantes a resolver los niveles abiertos con creatividad. La siguiente es una solución posible."
     solution_project_blurb: "Se alienta a los estudiantes a crear un proyecto creativo en este nivel. Consulta las guías curriculares del centro de recursos para más información sobre cómo evaluar estos proyectos."
-#    feedback_capstone: "The student will create a base project in Part 1. They will make creative updates based on peer feedback in Part 2. Here is a <a target=\"_blank\" rel=\"noopener\" href=\"https://docs.google.com/document/d/1itB0vY6WphjINQYJ9NHkILATcLLlYD3GDSg6FJg55OQ\">Peer Feedback Template</a> that you can share with your students."
+    feedback_capstone: "El estudiante creará un proyecto básico en la parte 1 y hará actualizaciones creativas a partir de los comentarios de sus compañeros en la parte 2. Puedes compartir esta <a target=\"_blank\" rel=\"noopener\" href=\"https://docs.google.com/document/d/1u8LtMHlWnpEgLZQtZomPfe82s-Swden7iIacCzdYYxk\">plantilla de comentarios</a> con tus estudiantes."
     students_code_blurb: "Cuando corresponde, se ofrece una solución correcta para el nivel. En algunos casos, es posible que el estudiante lo resuelva con un código distinto. No se muestran las soluciones de los niveles que el estudiante no ha comenzado."
     course_solution: "Solución del curso"
     level_overview_solutions: "Resumen del nivel y soluciones"
@@ -2117,14 +2117,14 @@
     sync_google_classroom: "Sincronizar Google Classroom"
     unit_overview: "Resumen de la unidad"
     module_overview: "Resumen del módulo"
-#    chapter1_unit_overview_desc: "Summary of student experience, including Cutscenes, Practice Levels, Intros, and the Capstone project in Chapter 1."
+    chapter1_unit_overview_desc: "Resumen de la experiencia del estudiante que incluye escenas de video, niveles de práctica, introducciones y el proyecto final del capítulo 1."
     lesson_plan: "Plan de estudios"
-#    chapter1_lesson_plan_desc: "Detailed classroom guide including learning objectives, CSTA standards, reflection activities, facilitation tips, and more."
+    chapter1_lesson_plan_desc: "Guía detallada que incluye objetivos de aprendizaje, normas CSTA, actividades de reflexión, sugerencias de facilitación y más."
     capstone_rubric: "Guía de evaluación del proyecto final"
-#    chapter1_capstone_rubric_desc: "Rubric for evaluating students' Capstone projects and reflection activities at the end of this unit."
+    chapter1_capstone_rubric_desc: "Guía para evaluar los proyectos finales y las actividades de reflexión de los estudiantes al final de la unidad."
     chapter1: "Capítulo 1: Bailarines de la luna" # {change}
-#    chapter2: "Chapter 2: The Moon Dancers"
-#    chapter3: "Chapter 3: The Phoenix Lands"
+    chapter2: "Capítulo 2: Bailarines de la luna"
+    chapter3: "Capítulo 3: Tierras de Phoenix"
     module1: "Módulo 1" # {change}
     module2: "Módulo 2" # {change}
     module3: "Módulo 3" # {change}
@@ -2137,25 +2137,25 @@
     conditionals: "Condicionales"
     capstone_intro: "Inicio del proyecto final"
     capstone_project: "Proyecto final"
-#    level_code: "Level Code"
-#    review_needed: "Review Needed"
-#    review_tooltip_heading: "What does this mean?"
-#    review_tooltip_text: "Red flags appear next to Concept Checks where your student(s) attempted to answer the question 3 or more times. In the <strong>Course Overview</strong> section, red flags appear next to Concept Checks if at least 50% of students who have started or completed the concept check attempted it 3 or more times."
-#    scroll_to_see_more: "Scroll to see more"
-#    click_to_view_concept_check: "Click to view concept check"
-#    click_to_view_project_gallery: "Click to view project gallery"
-#    review_learning_goal: "Review \"Learning Goal: __learningGoal__\" with students."
-#    view_capstone_project: "View Capstone Project"
-#    ozaria_dashboard_guide: "Teacher Dashboard Guide"
-#    dashboard_guide_desc: "Walkthrough of Ozaria Teacher Dashboard features that will help you view student progress and work."
-#    review: "Review"
-#    for_loops: "For Loops"
-#    peer_feedback: "Peer Feedback Form"
-#    presentation_checklist: "Presentation Checklist"
-
-#  teacher_licenses:
-#    active_licenses: "Active Licenses"
-#    expired_licenses: "Expired Licenses"
+    level_code: "Código de nivel"
+    review_needed: "Repaso necesario"
+    review_tooltip_heading: "¿Qué significa esto?"
+    review_tooltip_text: "Aparecerán señales de advertencia junto a las comprobaciones de conceptos en caso de que al menos un estudiante haya intentando responder la pregunta tres veces o más. En la sección <strong>Resumen del curso</strong>, aparecerán señales de advertencia junto a las comprobaciones de conceptos si al menos el 50% de los estudiantes que han comenzado o completado la comprobación lo intentaron tres veces o más."
+    scroll_to_see_more: "Desplázate para más información"
+    click_to_view_concept_check: "Haz clic para ver la comprobación de conceptos"
+    click_to_view_project_gallery: "Haz clic para ver la galería de proyectos"
+    review_learning_goal: "Repasar \"Meta de aprendizaje: __learningGoal__\" con los estudiantes."
+    view_capstone_project: "Ver proyecto final"
+    ozaria_dashboard_guide: "Guía de panel para maestros"
+    dashboard_guide_desc: "Un recorrido por las funciones del panel para maestros de Ozaria que te ayudará a supervisar el progreso y el trabajo de los estudiantes."
+    review: "Repaso"
+    for_loops: "Bucles for"
+    peer_feedback: "Formulario de comentarios"
+    presentation_checklist: "Lista de verificación para presentaciones"
+
+  teacher_licenses:
+    active_licenses: "Licencias activas"
+    expired_licenses: "Licencias vencidas"

   share_licenses:
     share_licenses: "Compartir licencias"
@@ -2171,9 +2171,9 @@
     you: "(tú)"
     one_license_used: "(1 licencia utilizada)"
     licenses_used: "(__licensesUsed__ licencias utilizadas)"
-#    licenses_used_no_braces: "__licensesUsed__ licenses used"
+    licenses_used_no_braces: "__licensesUsed__ licencias utilizadas"
     more_info: "Más información"
-#    shared_pool_label: "Teachers in your shared pool:"
+    shared_pool_label: "Maestros en tu conjunto compartido:"

   sharing:
     game: "Juego"
@@ -2605,7 +2605,7 @@
     unknown: "Error desconocido." # {change}
     error: "ERROR"
     general_desc: "Ocurrió un errór, probablemente es tu culpa. Espera un poco y luego refresca la página, o visíta uno de los siguientes enlaces para regresar a programar!"
-#    too_many_login_failures: "There have been too many unsuccessful login attempts.  Please try again later."
+    too_many_login_failures: "Hubo demasiados intentos de acceso fallidos.  Vuelve a intentarlo más tarde."

   resources:
     level: "Nivel"
@@ -2673,7 +2673,7 @@
     functions_parameters: "Parámetros"
     functions_multiple_parameters: "Múltiples Parámetros"
     game_ai: "IA de Juego"
-#    game_design: "Game Design"
+    game_design: "Diseño de juegos"
     game_goals: "Objetivos del Juego"
     game_spawn: "Generador del Juego"
     graphics: "Gráficos"
@@ -2702,10 +2702,10 @@
     math_operations: "Operaciones Matemáticas"
     math_proportions: "Proporciones Matemáticas"
     math_trigonometry: "Trigonometría"
-#    nesting: "Nesting"
+    nesting: "Anidamiento"
     object_literals: "Objetos Literales"
     parameters: "Parámetros"
-#    problem_solving: "Problem Solving"
+    problem_solving: "Solución de problemas"
     programs: "Programas"
     properties: "Propiedades"
     property_access: "Acceso a Propiedades"
@@ -2752,10 +2752,10 @@
     basic_javascript: "JavaScript Básico"
     basic_event_handling: "Manejo de Eventos Básico"
     create_sharable_interactive_web_page: "Crear página web interactiva compartible"
-#    iteration: "Iteration"
-#    data_types: "Data Types"
-#    objects: "Objects"
-#    methods: "Methods"
+    iteration: "Iteración"
+    data_types: "Tipos de datos"
+    objects: "Objetos"
+    methods: "Métodos"

   anonymous_teacher:
     notify_teacher: "Notificar al Maestro"
@@ -3062,7 +3062,7 @@
     send: "Enviar correo"

   hoc_2019:
-    heading: "Hora del Código 2020"
+    heading: "Hora del Código 2019"
     start: "Empieza tu camino"
     create_a_class: "¡Crea una clase para monitorear el progreso de los estudiantes y garantizar el guardado de sus juegos de la Hora del Código!"
     choose_language: "1. ELEGIR IDIOMA"
@@ -3083,8 +3083,8 @@
     dont_have: "¡No tengo, pero quiero probar la actividad!"
     hoc_activity: "Actividad de la Hora del Código"
     save_progress: "Guardar progreso"
-#    err_google_exists: "An account already exists for the Google account you've selected. Please choose another Google account or sign up with a username and password."
-#    err_username_taken: "An account already exists for the username you've entered. Please choose another username."
+    err_google_exists: "Ya existe una cuenta asociada a la cuenta de Google que seleccionaste. Elige otra cuenta de Google o inicia sesión con un nombre de usuario y una contraseña."
+    err_username_taken: "Ya existe una cuenta asociada al nombre de usuario que ingresaste. Elige otro nombre de usuario."

   hoc_2018:
     banner: "¡Feliz semana de la educación en ciencias de la computación de 2018!"


Small changes

Small changes

--- app/scripts/index.coffee
+++ app/scripts/index.coffee
@@ -98,9 +98,9 @@
   swapPlayers = ->
     $x = $ '#btn-player-x'
     $o = $ '#btn-player-o'
-    tmp = $x.html()
-    $x.html $o.html()
-    $o.html tmp
+    tmp = $x.text()
+    $x.text $o.text()
+    $o.text tmp

   $ '.player'
     .on 'click', ->
@@ -169,7 +169,7 @@
   setup = ->
     teardown()
     game = new UltimateTicTacToe
-    game.lastPlayedPosition = null
+    game.lastPlayedPosition = null # start with the full board open
     lastAction = null
     [playerX, playerO] = [createPlayerX(), createPlayerO()]
     playerX.setup -> playerO.setup -> next()
@@ -185,7 +185,6 @@
       .removeClass 'x-won-tile'
       .removeClass 'o-won-tile'
       .text ''
-    ($ '#info-container').hide()

   $ '#btn-new-game'
     .on 'click', ->


fix test suite to run in Chrome

fix test suite to run in Chrome

--- app/scripts/menus.coffee
+++ app/scripts/menus.coffee
@@ -1,49 +1,50 @@
 # nw.gui requires it to be named require
 require = window.requireNode
-gui = require('nw.gui')
-win = gui.Window.get()
-menubar = new gui.Menu(type: 'menubar')
+if require?
+  gui = require('nw.gui')
+  win = gui.Window.get()
+  menubar = new gui.Menu(type: 'menubar')

-onMac = process.platform == 'darwin'
-if onMac
-  menubar.createMacBuiltin('Daemon')
-debugMenu = new gui.Menu()
+  onMac = process.platform == 'darwin'
+  if onMac
+    menubar.createMacBuiltin('Daemon')
+  debugMenu = new gui.Menu()

-debugMenu.append new gui.MenuItem(
-  label: 'Reload'
-  click: -> win.reload()
-  key: 'r'
-  modifiers: if onMac then 'cmd' else 'ctrl'
-  )
+  debugMenu.append new gui.MenuItem(
+    label: 'Reload'
+    click: -> win.reload()
+    key: 'r'
+    modifiers: if onMac then 'cmd' else 'ctrl'
+    )

-debugMenu.append new gui.MenuItem(
-  label: 'Reload and Clear Cache'
-  click: -> win.reloadIgnoringCache()
-  key: 'r'
-  modifiers: if onMac then 'cmd-shift' else 'shift-ctrl'
-  )
+  debugMenu.append new gui.MenuItem(
+    label: 'Reload and Clear Cache'
+    click: -> win.reloadIgnoringCache()
+    key: 'r'
+    modifiers: if onMac then 'cmd-shift' else 'shift-ctrl'
+    )

-debugMenu.append new gui.MenuItem(
-  label: "Toggle Kiosk Mode"
-  click: -> win.toggleKioskMode()
-  key: 'f'
-  modifiers: if onMac then 'cmd-shift' else 'shift-ctrl'
-  )
+  debugMenu.append new gui.MenuItem(
+    label: "Toggle Kiosk Mode"
+    click: -> win.toggleKioskMode()
+    key: 'f'
+    modifiers: if onMac then 'cmd-shift' else 'shift-ctrl'
+    )

-debugMenu.append new gui.MenuItem type: 'separator'
+  debugMenu.append new gui.MenuItem type: 'separator'

-debugMenu.append new gui.MenuItem(
-  label: 'Developer Tools'
-  click: -> win.showDevTools()
-  key: 'i'
-  modifiers: if onMac then 'cmd-alt' else 'shift-ctrl'
-  )
+  debugMenu.append new gui.MenuItem(
+    label: 'Developer Tools'
+    click: -> win.showDevTools()
+    key: 'i'
+    modifiers: if onMac then 'cmd-alt' else 'shift-ctrl'
+    )

-menubar.append new gui.MenuItem(
-  label: 'Developer'
-  submenu: debugMenu
-  )
-win.menu = menubar
+  menubar.append new gui.MenuItem(
+    label: 'Developer'
+    submenu: debugMenu
+    )
+  win.menu = menubar

 angular.module('menubar', [])


[Gutter] Convert GutterContainerComponent::updateChildGutters -> ::reorderGutters

--- src/gutter-container-component.coffee
+++ src/gutter-container-component.coffee
@@ -38,10 +38,11 @@
         else
           gutterComponent = new CustomGutterComponent({gutter})
       if visible then gutterComponent.showNode() else gutterComponent.hideNode()
+      gutterComponent.updateSync(state)
       newGutterComponents.push(gutterComponent)
       newGutterComponentsByGutterName[gutter.name] = gutterComponent

-    @updateChildGutters(state, newGutterComponents, newGutterComponentsByGutterName)
+    @reorderGutters(newGutterComponents, newGutterComponentsByGutterName)

     @gutterComponents = newGutterComponents
     @gutterComponentsByGutterName = newGutterComponentsByGutterName
@@ -50,12 +51,12 @@
   Section: Private Methods
   ###

-  updateChildGutters: (state, newGutterComponents, newGutterComponentsByGutterName) ->
+  reorderGutters: (newGutterComponents, newGutterComponentsByGutterName) ->
     # First, insert new gutters into the DOM.
     indexInOldGutters = 0
     oldGuttersLength = @gutterComponents.length
+
     for gutterComponent in newGutterComponents
-      gutterComponent.updateSync(state)
       if @gutterComponentsByGutterName[gutterComponent.getName()]
         # If the gutter existed previously, we first try to move the cursor to
         # the point at which it occurs in the previous gutters.


Added namespace for siteconfig snippet

Added namespace for siteconfig snippet

--- completions.cson
+++ completions.cson
@@ -3045,6 +3045,15 @@
     }
     {
       'scope': '.text.html.php'
+      'minVersion': '4.0'
+      'body': 'SiteConfig::current_site_config()'
+      'useNamespace': [
+        'SilverStripe\\SiteConfig\\SiteConfig'
+      ]
+    }
+    {
+      'scope': '.text.html.php'
+      'maxVersion': '4.0'
       'body': 'SiteConfig::current_site_config()'
     }
   ]


EnvironmentListItem: Make params parameter optional

--- client/app/lib/environment/environmentlistitem.coffee
+++ client/app/lib/environment/environmentlistitem.coffee
@@ -125,7 +125,7 @@
       @sendItemAction 'StackDeleteRequested', { item : this }

-  sendItemAction: (action, params) ->
+  sendItemAction: (action, params = {}) ->

     params.action = action


remove margin on icon

remove margin on icon

--- client/lanes/components/select-field/SelectField.cjsx
+++ client/lanes/components/select-field/SelectField.cjsx
@@ -143,7 +143,7 @@
                 {@renderSelectControl(props)}
                 <BS.InputGroup.Button>
                     <button className='btn' onClick={@clearSelectedValue}>
-                        <LC.Icon type='undo' tooltip="Clear Selection"
+                        <LC.Icon type='undo' flush tooltip="Clear Selection"
                             tooltipProps={trigger: ['hover', 'focus']} />
                     </button>
                 </BS.InputGroup.Button>


pass event arguments to onEntry callback

--- ng-modules/Nodule.coffee
+++ ng-modules/Nodule.coffee
@@ -8,11 +8,11 @@
             @onExit = onExit
         @isActive = false

-        @scope.$on('switchToModule', (event, nextModule)=>
+        @scope.$on('switchToModule', (event, nextModule, args...)=>
             if not @isActive
                 if nextModule == @name  # if switching to this module
                     @isActive = true
-                    @onEntry()
+                    @onEntry args...
             else
                 # switching from this module to another
                 @isActive = false


Implement votes API call.

Implement votes API call.

--- routes/api/v1/votes.coffee
+++ routes/api/v1/votes.coffee
@@ -13,7 +13,37 @@
 #
 #    You should have received a copy of the GNU General Public License
 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ObjectId = require('mongodb').ObjectID;
+
 module.exports = (app, db) ->
   # GET /api/v1/votes - Retrieve all votes.
   app.get '/api/v1/votes', (req, res) ->
-    res.send 501
+    db.collection("sessions").findOne {current: true}, (err, currentSession) ->
+      unless currentSession?
+        err = "Could not find current session."
+        errorId = Math.random().toString(36).substring(7)
+        console.error("Error " + errorId + ": " + err)
+
+        res.json
+          id: errorId,
+          error: err
+        , 500
+
+        return
+
+      selectedSessionIdStr = req.query.sessionId || currentSession._id.toString()
+      selectedSessionId = new ObjectId(selectedSessionIdStr)
+
+      db.collection("votes").find({sessionId: selectedSessionId}).toArray (err, results) ->
+        if err
+          errorId = Math.random().toString(36).substring(7)
+          console.error("Error " + errorId + ": " + err)
+
+          res.json
+            id: errorId,
+            error: err
+          , 500
+
+          return
+
+        res.json(results)


increase some caching timeouts

increase some caching timeouts

--- salvus/cassandra.coffee
+++ salvus/cassandra.coffee
@@ -778,7 +778,7 @@

     # all_users: cb(err, array of {first_name:?, last_name:?, account_id:?, search:'names and email thing to search'})
     #
-    # No matter how often all_users is called, it is only updated at most once every 60 seconds, since it is expensive
+    # No matter how often all_users is called, it is only updated at most once every 5 minutes, since it is expensive
     # to scan the entire database, and the client will typically make numerous requests within seconds for
     # different searches.  When some time elapses and we get a search, if we have an old cached list in memory, we
     # use it and THEN start computing a new one -- so user queries are always answered nearly instantly, but only
@@ -824,7 +824,7 @@
                 @_all_users_fresh = true
                 f = () =>
                     delete @_all_users_fresh
-                setTimeout(f, 60000)   # cache for 1 minute
+                setTimeout(f, 5*60000)   # cache for 5 minutes

     user_search: (opts) =>
         opts = defaults opts,
@@ -1288,7 +1288,7 @@
                             banned_accounts = @_account_is_banned_cache
                             f = () =>
                                 delete @_account_is_banned_cache
-                            setTimeout(f, 60000)    # cache db lookups for 1 minute
+                            setTimeout(f, 10*60000)    # cache db lookups for 10 minutes
                             cb()
             (cb) =>
                 if opts.email_address?


fix a type error

fix a type error

--- salvus/page/r_misc.cjsx
+++ salvus/page/r_misc.cjsx
@@ -520,7 +520,7 @@
     displayName : "ActivityDisplay"

     propTypes :
-        activity : rtypes.object.isRequired  # array of strings
+        activity : rtypes.array.isRequired   # array of strings
         trunc    : rtypes.number             # truncate activity messages at this many characters (default: 80)
         on_clear : rtypes.func               # if given, called when a clear button is clicked


Add another test case.

Add another test case.

--- spec/jumpy-multi-pane-spec.coffee
+++ spec/jumpy-multi-pane-spec.coffee
@@ -45,3 +45,4 @@
     # should be enough to make this red to green.
     describe "when jumpy jumps to another pane", ->
         it "focuses the new pane", ->
+        it "does not move cursor of original pane", ->


Fix test

Fix test

--- demo_server/src/test.coffee
+++ demo_server/src/test.coffee
@@ -19,51 +19,11 @@
 # TODO
 # Refactor the test functions

-state =
-    1:
-        test:
-            test:
-                data:
-                    "N1":
-                        id: 1
-                    "N2":
-                        id: 2
-                    "N3":
-                        id: 3
-    2:
-        test:
-            test:
-                data:
-                    "N1":
-                        id: 1
-                        key: 1
-                    "N2":
-                        id: 2
-                        key: 2
-                    "N3":
-                        id: 3
-                        key: 3
-                options:
-                    'primary_key': 'id'
-    3:
-        test:
-            test:
-                data:
-                    "N2":
-                        id: 2
-                        key: 3
-                    "N5":
-                        id: 5
-                        key: 3
-                    "N4":
-                        id: 4
-                        key: 1
-                    "N1":
-                        id: 1
-                        key: 1
-                    "N3":
-                        id: 3
-                        key: 2
+collection = [
+    [{id:1}, {id:2}, {id:3}],
+    [{id:1, key: 4}, {id:2, key: 5}, {id:3, key: 6}],
+    [{id:2, key: 3}, {id:5, key: 3}, {id:4, key: 1}, {id:1, key:1}, {id:3, key: 2}]
+]

 class Tests
     results: []
@@ -76,13 +36,15 @@
         query = @queries[@index]
         if query?
             console.log query.query
+            if query.collection?
+                r.dbDrop('test').run()
+                r.dbCreate('test').run()
+                r.db('test').tableCreate('test').run()
+                for element in query.collection
+                    r.db('test').table('test').insert(element).run()
+
             cursor = eval(query.query)
             @callback()[query.callback_name](query.query, cursor, query.expected_result, @)
-
-            ###
-            @index++
-            @test()
-            ###

     display: (success, query, results) ->
         if success is true
@@ -304,16 +266,6 @@
                 that.index++
                 that.test()
                 return false
-        is_state_1: (query, cursor, expected_result, that) =>
-            @results = []
-            cursor.next (data) =>
-                if data?
-                    @results.push data
-                    return true
-                @display _.isEqual(demo_server.dbs, state[1]), query, @results
-                that.index++
-                that.test()
-                return false
         is_doc_1: (query, cursor, expected_result, that) =>
             @results = []
             cursor.next (data) =>
@@ -331,16 +283,6 @@
                     @results.push data
                     return true
                 @display (_.isEqual(@results, expected_result)), query, @results
-                that.index++
-                that.test()
-                return false
-        expect_state: (query, cursor, expected_state, that) =>
-            @results = []
-            cursor.next (data) =>
-                if data?
-                    @results.push data
-                    return true
-                @display (_.isEqual(demo_server.dbs?.test.tables.test, expected_state)), query, demo_server.dbs?.test.tables.test
                 that.index++
                 that.test()
                 return false
@@ -664,157 +606,160 @@
             callback_name: 'empty_answer'
         },
         {
-            state: deep_copy state['1']
+            collection: collection[0]
             query: 'r.db("test").table("test").filter(r.expr(true)).run()'
-            callback_name: 'is_state_1'
-        },
-        {
-            state: deep_copy state['1']
+            callback_name: 'expect_result'
+            expected_result: collection[0]
+        },
+        {
+            collection: collection[0]
             query: 'r.db("test").table("test").filter(r("@").eq({id:1})).run()'
             callback_name: 'is_doc_1'
-        },
-        {
-            state: deep_copy state['1']
+        }
+        {
+            collection: collection[0]
             query: 'r.db("test").table("test").filter(r("@")("id").eq(1)).run()'
             callback_name: 'is_doc_1'
         },
         {
-            state: deep_copy state['1']
+            collection: collection[0]
             query: 'r.db("test").table("test").filter(r("id").eq(1)).run()'
             callback_name: 'is_doc_1'
         },
         {
-            state: deep_copy state['1']
+            collection: collection[0]
             query: 'r.db("test").table("test").between(2, 3).run()'
             callback_name: 'expect_result'
             expected_result: [{id:2}, {id:3}]
         },
         {
-            state: deep_copy state['2']
-            query: 'r.db("test").table("test").get(2).replace({id:2, key:"new_value", other_key:"new_other_value"}).run()'
-            callback_name: 'two_has_been_replaced'
-        },
-
-        {
-            state: deep_copy state['2']
-            query: 'r.db("test").table("test").del().run()'
-            callback_name: 'table_empty'
-        },
-        {
-            state: deep_copy state['2']
-            query: 'r.db("test").table("test").replace({id:2, key:"new_value", other_key:"new_other_value"}).run()'
-            callback_name: 'two_has_been_replaced'
-        },
-        {
-            state: deep_copy state['2']
-            query: 'r.db("test").table("test").update({key:0}).run()'
-            callback_name: 'expect_state'
-            expected_result: {"N1": {id:1, key:0}, "N2": {id:2, key:0}, "N3": {id:3, key:0}}
-        },
-        {
-            state: deep_copy state['2']
-            query: 'r.db("test").table("test").filter(r("id").ge(2)).replace({id:2, key:"new_value", other_key:"new_other_value"}).run()'
-            callback_name: 'two_has_been_replaced_and_filter'
-        },
-        {
-            state: deep_copy state['2']
-            query: 'r.db("test").table("test").filter(r("id").ge(2)).del().run()'
-            callback_name: 'expect_state'
-            expected_result: { "N1": {id:1, key:1} }
-        },
-        {
-            state: deep_copy state['1']
+            collection: deep_copy collection[0]
+            query: 'r.db("test").table("test").get(2).replace({id:2, key:"new_value", other_key:"new_other_value"}).run(); r.db("test").table("test").get(2).run()'
+            callback_name: 'expect_result'
+            expected_result: [ {id:2, key:"new_value", other_key:"new_other_value"} ]
+        },
+        {
+            collection: deep_copy collection[0]
+            query: 'r.db("test").table("test").del().run(); r.db("test").table("test").run()'
+            callback_name: 'expect_result'
+            expected_result: []
+        },
+        {
+            collection: deep_copy collection[0]
+            query: 'r.db("test").table("test").replace({id:2, key:"new_value", other_key:"new_other_value"}).run(); r.db("test").table("test").orderBy("id").run()'
+            callback_name: 'expect_result'
+            expected_result: [ {id:1}, {id:2, key:"new_value", other_key:"new_other_value"}, {id:3} ]
+        },
+        {
+            collection: deep_copy collection[1]
+            query: 'r.db("test").table("test").update({key:0}).run(); r.db("test").table("test").run()'
+            callback_name: 'expect_result'
+            expected_result: [ {id:1, key:0}, {id:2, key:0}, {id:3, key:0} ]
+        },
+        {
+            collection: deep_copy collection[0]
+            query: 'r.db("test").table("test").filter(r("id").ge(2)).replace({id:2, key:"new_value", other_key:"new_other_value"}).run(); r.db("test").table("test").orderBy("id").run()'
+            callback_name: 'expect_result'
+            expected_result: [ {id:1}, {id:2, key:"new_value", other_key:"new_other_value"}, {id:3} ]
+        },
+        {
+            collection: deep_copy collection[0]
+            query: 'r.db("test").table("test").filter(r("id").ge(2)).del().run(); r.db("test").table("test").run()'
+            callback_name: 'expect_result'
+            expected_result: [ {id: 1} ]
+        },
+        {
+            collection: deep_copy collection[0]
             query: 'r.db("test").table("test").filter(r("id").le(2)).between(2, 3).run()'
             callback_name: 'expect_result'
             expected_result: [{id:2}]
         },
         {
-            state: deep_copy state['2']
-            query: 'r.db("test").table("test").filter(r("id").eq(1)).update({key:0}).run()'
-            callback_name: 'expect_state'
-            expected_result: {"N1": {id:1, key:0}, "N2": {id:2, key:2}, "N3": {id:3, key:3}}
-        },
-        {
-            state: deep_copy state['2']
+            collection: deep_copy collection[1]
+            query: 'r.db("test").table("test").filter(r("id").eq(1)).update({key:0}).run(); r.db("test").table("test").run()'
+            callback_name: 'expect_result'
+            expected_result: [ {id:1, key:0}, {id:2, key:5}, {id:3, key:6} ]
+        },
+        {
+            collection: deep_copy collection[0]
             query: 'r.db("test").table("test").filter(function(doc) { return r.branch(doc("id").gt(2), r.expr(true), r.expr(false)) }).run()'
             callback_name: 'expect_result'
-            expected_result: [{id:3, key:3}]
-        },
-        {
-            state: deep_copy state['2']
-            query: 'r.db("test").table("test").filter(function(doc) { return doc.eq({id:2, key:2}) }).run()'
-            callback_name: 'expect_result'
-            expected_result: [{id:2, key:2}]
-        },
-       {
-            state: deep_copy state['2']
+            expected_result: [{id:3}]
+        },
+        {
+            collection: deep_copy collection[1]
+            query: 'r.db("test").table("test").filter(function(doc) { return doc.eq({id:2, key:5}) }).run()'
+            callback_name: 'expect_result'
+            expected_result: [{id:2, key:5}]
+        },
+        {
+            collection: deep_copy collection[0]
             query: 'r.db("test").table("test").map(r("id")).run()'
             callback_name: 'expect_result'
             expected_result: [1, 2, 3]
         },
         {
-            state: deep_copy state['2']
+            collection: deep_copy collection[0]
             query: 'r.db("test").table("test").map("id").run()'
             callback_name: 'expect_result'
             expected_result: ['id', 'id', 'id']
         },
         {
-            state: deep_copy state['2']
+            collection: deep_copy collection[0]
             query: 'r.db("test").table("test").map(function(doc) { return doc("id")}).run()'
             callback_name: 'expect_result'
             expected_result: [1, 2, 3]
         },
         {
-            state: deep_copy state['2']
+            collection: deep_copy collection[0]
             query: 'r.db("test").table("test").map(function(doc) { return doc("id").add(10)}).run()'
             callback_name: 'expect_result'
             expected_result: [11, 12, 13]
         },
         {
-            state: deep_copy state['2']
+            collection: deep_copy collection[0]
             query: 'r.db("test").table("test").concatMap(function(doc) { return r.expr([1]) }).run()'
             callback_name: 'expect_result'
-            expected_result: [[1, 1, 1]]
-        },
-        {
-            state: deep_copy state['2']
+            expected_result: [1, 1, 1]
+        },
+        {
+            collection: deep_copy collection[0]
             query: 'r.db("test").table("test").concatMap(function(doc) { return r.expr([r.expr(r("id"))]) }).run()'
             callback_name: 'expect_result'
-            expected_result: [[1, 2, 3]]
-        },
-
-        {
-            state: deep_copy state['1']
+            expected_result: [1, 2, 3]
+        },
+        {
+            collection: deep_copy collection[0]
             query: 'r.db("test").table("test").skip(2).run()'
             callback_name: 'expect_result'
             expected_result: [{id: 3}]
         },
         {
-            state: deep_copy state['1']
+            collection: deep_copy collection[0]
             query: 'r.db("test").table("test").limit(2).run()'
             callback_name: 'expect_result'
             expected_result: [{id: 1}, {id:2}]
         },
         {
-            state: deep_copy state['1']
+            collection: deep_copy collection[0]
             query: 'r.db("test").table("test").skip(1).limit(1).run()'
             callback_name: 'expect_result'
             expected_result: [{id:2}]
         },
         {
-            state: deep_copy state['1']
+            collection: deep_copy collection[0]
             query: 'r.db("test").table("test").limit(1).skip(1).run()'
             callback_name: 'expect_result'
             expected_result: []
         },
         {
-            state: deep_copy state['3']
+            collection: deep_copy collection[2]
             query: 'r.db("test").table("test").orderBy("key", "id").run()'
             callback_name: 'expect_result'
             expected_result: [{id:1,key:1},{id:4,key:1},{id:3,key:2},{id:2,key:3},{id:5,key:3}]
         },
         {
-            state: deep_copy state['3']
+            collection: deep_copy collection[2]
             query: 'r.db("test").table("test").orderBy(r.desc("key"), r.asc("id")).run()'
             callback_name: 'expect_result'
             expected_result: [{id:2,key:3},{id:5,key:3},{id:3,key:2},{id:1,key:1},{id:4,key:1}]


The test beautification I always wanted

The test beautification I always wanted

--- spec/serverAcceptance.spec.coffee
+++ spec/serverAcceptance.spec.coffee
@@ -3,42 +3,40 @@
 {ServerTester} = require './helper/serverTester'
 PORT = 4050

-ITEM_ID = "I'm a String ID - weird"
+ITEM_ID = '1'
+TEAM_ID = 1
+VERSION = '1.0'
+RESULT_MESSAGE = 'result'

 testJSON = ->
-  '{
-    "msg":"impression",
-    "id":2,
-    "client":{
-      "id":1
-    },
-    "domain":{
-      "id":1
-    },
-    "item":{
-    	"id": "weirdly enough Im a string",
-    	"title":"muuh",
-    	"url":"google.de",
-    	"created":42,
-    	"text":"a cow",
-    	"img":"google.de/images.jpg",
-    	"recommendable":true
-    },
-    "context":{
-    	"category":{
-    		"id":77
-    	}
-    },
-    "config":{
-    	"team":{
-    		"id":2
-    	},
-    	"timeout":200.0,
-    	"recommend":true,
-    	"limit":5
-    },
-    "version": "1.0"
-  }'
+  msg:"impression"
+  id:2
+  client:
+    id:1
+  domain:
+    id:1
+  item:
+    id: ITEM_ID
+    title:"muuh"
+    url:"google.de"
+    created: 42
+    text:"a cow"
+    img:"google.de/images.jpg"
+    recommendable:true
+  context:
+  	category:
+  		id: 77
+  config:
+  	team:
+  		id: TEAM_ID
+  	timeout:200.0
+  	recommend:true
+  	limit:5
+  version: VERSION
+
+
+testJSONString = ->
+  JSON.stringify(testJSON())

 recommender = new Recommender(new ItemStorage)
 helper = new ServerTester recommender, PORT
@@ -46,19 +44,19 @@
 describe 'Acceptance tests for server and recommendation engine', ->

   it 'handles the example JSON well and does not respond when items are empty', ->
-    helper.sendAndExpect testJSON(), (responseObject) ->
+    helper.sendAndExpect testJSONString(), (responseObject) ->
       expect(responseObject.items).toEqual([])

   it 'sets the correct version number', ->
-    helper.sendAndExpect testJSON(), (responseObject) ->
-      expect(responseObject.version).toEqual("1.0")
+    helper.sendAndExpect testJSONString(), (responseObject) ->
+      expect(responseObject.version).toEqual(VERSION)

   it 'sets the correct tem id', ->
-    helper.sendAndExpect testJSON(), (responseObject) ->
-      expect(responseObject.team.id).toEqual(2)
+    helper.sendAndExpect testJSONString(), (responseObject) ->
+      expect(responseObject.team.id).toEqual(TEAM_ID)

   it 'sets the right msg type', ->
-    helper.sendAndExpect testJSON(), (responseObject) ->
-      expect(responseObject.msg).toEqual('result')
+    helper.sendAndExpect testJSONString(), (responseObject) ->
+      expect(responseObject.msg).toEqual(RESULT_MESSAGE)

 runs -> helper.stopServer()


[browser] Add Application#isDevMode method

--- src/browser/Application.coffee
+++ src/browser/Application.coffee
@@ -94,6 +94,13 @@
         return

     ###
+    Application state
+    ###
+
+    isDevMode       : ->
+        !!@options.devMode
+
+    ###
     Window Managements
     ###


Remove unused function

Remove unused function

--- src/browser/application.coffee
+++ src/browser/application.coffee
@@ -57,8 +57,6 @@
     menu.on 'application:run-specs', =>
       @openWithOptions(test: true)

-  reload: -> @window.reload()
-
   exit: (status) -> app.exit(status)

   # Opens up a new {AtomWindow} to run specs within.


Unbreak check_fd callback

Unbreak check_fd callback

--- common/lib/xmodule/xmodule/js/src/capa/display.coffee
+++ common/lib/xmodule/xmodule/js/src/capa/display.coffee
@@ -46,9 +46,11 @@
       $(placeholder).remove()

   ###
-  'check_fd' uses FormData to allow file submissions through an AJAX call.
-  NOTE: The dispatch 'problem_check' is being singled out for the use of FormData;
-        can consolidate all dispatches to use FormData consistently
+  # 'check_fd' uses FormData to allow file submissions, in addition to simple,
+  #      querystring-based answers, in the 'problem_check' dispatch.
+  #
+  # NOTE: The dispatch 'problem_check' is being singled out for the use of FormData;
+  #       perhaps preferrable to consolidate all dispatches to use FormData consistently
   ###
   check_fd: =>
     Logger.log 'problem_check', @answers
@@ -66,20 +68,21 @@

     # Simple (non-file) answers,
     #   routed to Django 'request.POST'
-    fd.append('answers', @answers)
+    fd.append('_answers_querystring', @answers)

     settings =
       type: "POST"
       data: fd
       processData: false
       contentType: false
-      success: (response) ->
+      success: (response) =>
         switch response.success
           when 'incorrect', 'correct'
             @render(response.contents)
             @updateProgress response
           else
             alert(response.success)
+
     $.ajaxWithPrefix("#{@url}/problem_check", settings)

   check: =>


Add more keywords

Add more keywords

--- grammars/protosnirk.cson
+++ grammars/protosnirk.cson
@@ -41,7 +41,7 @@
     }
     'keyword_declaration': {
         'name': 'keyword.control.protosnirk'
-        'match': '\\b(use|import|public|struct|class|fn|trait|provider)\\b'
+        'match': '\\b(use|import|public|struct|class|trait|enum|fn|strong|weak)\\b'
     }
     'builtin_type': {
         'name': 'keyword.other.protosnirk'


Copy scripts into sample/www/scripts.

Copy scripts into sample/www/scripts.

--- build/jakeGame.iced
+++ build/jakeGame.iced
@@ -1,9 +1,10 @@
 fs = require 'fs'
+glob = require 'glob'
 path = require 'path'
 Paths = require './paths'

 root = path.resolve __dirname, '..'
-paths = new Paths root, 'runtime', 'runtime/view'
+paths = new Paths root, 'runtime', 'runtime/js', 'runtime/view'

 directory 'www'
 file 'www/index.html', ['www', 'game.json', paths.runtime.view 'index.chtml'], ->
@@ -15,4 +16,23 @@
     output = eco.render template, gameData
     fs.writeFileSync 'www/index.html', output

-task 'default', ['www/index.html']+directory 'www/scripts'
+scriptSourcePattern = paths.runtime.js('*.js')
+# glob doesn't work with drive letters. Make sure to use relative path.
+scriptSourcePattern = path.relative(process.cwd(), scriptSourcePattern)
+# glob doesn't work with backslashes. Make sure to use forward slashes.
+scriptSourcePattern = scriptSourcePattern.replace /[\\]/g, '/'
+scriptSourcePaths = glob.sync scriptSourcePattern
+scriptTargetPaths = []
+for sourcePath in scriptSourcePaths
+    targetPath = path.join 'www', 'scripts', path.basename sourcePath
+    scriptTargetPaths.push targetPath
+    do (sourcePath, targetPath) ->
+        file targetPath, [sourcePath, 'www/scripts'], ->
+            jake.cpR sourcePath, targetPath
+
+task 'html', ['www/index.html']
+
+task 'scripts', scriptTargetPaths
+
+task 'default', ['html', 'scripts']


bug fix

bug fix

--- server/router.coffee
+++ server/router.coffee
@@ -288,7 +288,7 @@
     ).fetch()
   # sort events so they are returned in same order as the ids
   events = eventIds.map (eventId)->
-    events.findOne(eventId)
+    _.findWhere(events, _id: eventId)
   events.forEach (event) ->
     if event.incidents
       query =


Add twitter login

Add twitter login

--- server/routes.coffee
+++ server/routes.coffee
@@ -3,6 +3,7 @@

 routes =
   index: (req, res) ->
+    console.log req.user
     res.render 'index'
   login: (req, res) ->
     if req.isAuthenticated()
@@ -14,10 +15,39 @@
     res.redirect '/'
   twitterAuthCallback: (req, res) ->
     # Create Product Grunt user if doesn't exist
-    # Add Product Grunt user to req.user
-    res.send(req.user)
-  welcome: (req, res) ->
-    res.render 'welcome'
+    twitterId = req.user.twitterId
+    User.findOne twitterId: twitterId, (err, data) ->
+      if !data
+        # Add user data
+        req.user.twitterId = req.user.id
+        req.user.twitterUsername = req.user.username
+        photoURL = req.user.photos[0].value
+        req.user.twitterProfilePicture = photoURL.split('_normal').join('')
+        req.user.name = req.user.displayName
+        req.user.bio = req.user._json.description
+
+        newUser = new User
+          twitterId: req.user.twitterId
+          twitterUsername: req.user.twitterUsername
+          twitterProfilePicture: req.user.twitterProfilePicture
+          name: req.user.name
+          bio: req.user.bio
+          uid: req.user.uid
+
+        console.log req.user
+
+        newUser.save (err, newUserSave) ->
+          res.send '<meta http-equiv="refresh" content="0; url=/" />'
+      else
+        # User has data, add it
+        console.log data
+        req.user.twitterId = data.twitterId
+        req.user.twitterUsername = data.twitterUsername
+        req.user.twitterProfilePicture = data.twitterProfilePicture
+        req.user.name = data.name
+        req.user.bio = data.bio
+
+        res.send '<meta http-equiv="refresh" content="0; url=/" />'
   about: (req, res) ->
     res.render 'about'
   user: (req, res) ->


Log no start jobs

Log no start jobs

--- server/server.coffee
+++ server/server.coffee
@@ -75,5 +75,7 @@
         if not (process.env["INSTANCE_NUMBER"]?) or (process.env["INSTANCE_NUMBER"] == 0)
             logger.info("Starting jobs")
             jobs.map((job) -> job.start())
+        else
+            logger.info("Will not start jobs")

 start()


User port from openshift settings

User port from openshift settings

--- server/server.coffee
+++ server/server.coffee
@@ -70,5 +70,6 @@

 app.use renderOnServer

-app.listen 3000, () ->
-  logger.info 'Example app listening on port 3000!'
+port = (process.env.OPENSHIFT_NODEJS_PORT || 3000)
+app.listen port, () ->
+  logger.info 'Example app listening on port ', port


Fixing the error middleware so that the logic is allowed to continue

--- src/middleware/ErrorMiddleware.coffee
+++ src/middleware/ErrorMiddleware.coffee
@@ -32,6 +32,8 @@
         json:()=>
           @sendJsonResponse(err, req, res)

+      next()
+
     logErrorStack: (err)->
       statusCode = err.statusCode or 0


fix(workshop-page): turn links into html

--- src/components/Workshop.coffee
+++ src/components/Workshop.coffee
@@ -14,32 +14,32 @@

   render: ->
     div className: "info",
-     h2 null,
+    h2 null,
         "Weiterführende Infos zur Karte von morgen"
         br null
-     h3 null,
+    h3 null,
         "Aktuelles"
         br null
-      p null
-     a href: "", onClick: (-> onClick V.JOIN), ""
-        "Neugikeiten und aktuelles auf "  a href: "https://www.facebook.com/vonmorgen", onClick: (-> onClick V.JOIN), "Facebook"
-        "Fortschrittsberichte auf "  a href: "https://www.betterplace.org/de/projects/36213/newsroom", onClick: (-> onClick V.JOIN), "unserem Blog auf Betterplace"
+        p null
+    a href: "", onClick: (-> onClick V.JOIN), ""
+        # "Neuigkeiten und aktuelles auf Facebook"
+        # "Fortschrittsberichte auf unserem Blog auf Betterplace"
         br null
      h3 null,
         "Hintergründe und Visionen"
         br null
         p null
-        "Was will die " a href: "http://bildungsagenten.org/kartevonmorgen/", onClick: (-> onClick V.JOIN), "Karte von morgen?"
-        "- Was sind die " a href: "http://bildungsagenten.org/kartevonmorgen/2/", onClick: (-> onClick V.JOIN), "Positivfaktoren?"
-        "- Was sind " a href: "http://bildungsagenten.org/kartevonmorgen/3/", onClick: (-> onClick V.JOIN), "Regionalpiloten bzw.  Themenpiloten? Wie nutze ich Widgets?"
-        "- Wie kann ich " a href: "http://bildungsagenten.org/kartevonmorgen/4/", onClick: (-> onClick V.JOIN), "mitmachen oder die Karte von morgen unterstützen?"
-     img style: {float: "left"}, src: require "../img/Team an Tisch.jpg"
+        "Was will die Karte von morgen?"
+        "- Was sind die Positivfaktoren?"
+        "- Was sind Regionalpiloten bzw.  Themenpiloten? Wie nutze ich Widgets?"
+        "- Wie kann ich mitmachen oder die Karte von morgen unterstützen?"
+    img style: {float: "left"}, src: require "../img/Team an Tisch.jpg"

-     h3 null,
+    h3 null,
         "Spenden"
         br null
         p null
-        "Einfach online Spenden über "  a href: "https://www.betterplace.org/de/projects/36213", onClick: (-> onClick V.JOIN), "Betterplace"
+        "Einfach online Spenden über Betterplace"
          br null
         "Etwas Gutes für morgen tun: Die Plattform von morgen finanziert sich über Fördermittel verschiedener Programme
         und Wettbewerbe aber zum Großteil über Spenden."
@@ -48,16 +48,16 @@
         "Wir freuen uns über jeden kleinen und großen Beitrag und hoffen auch bald in deiner Stadt verfügbar zu sein. Auf bald!"
         p null
         "Dankend, das Team von morgen"
-     img style: {float: "left"}, src: require "../img/Eam-in-Action.jpg"
-     h3 null,
+    img style: {float: "left"}, src: require "../img/Eam-in-Action.jpg"
+    h3 null,
         "Workshops von morgen"
         br null
         "Gemeinsam mit unserem Partner, dem gemeinnützigen Verein Ideen³ e.V. und der „Ideenwerkstatt Bildungsagenten”,
         bieten wir Workshops zu verschiedenen Themen an.
         www.bildungsagenten.org"
-     img style: {float: "left"}, src: require "../img/Slider.jpg "
-      p className: "version",
+    img style: {float: "left"}, src: require "../img/Slider.jpg "
+        p className: "version",
         "Version dieses Clients: v#{pkg.version}"
       if (sv = @props.server?.version)?
         p className: "version",
-          "Version des Servers: v#{sv}"
+        "Version des Servers: v#{sv}"


Added comment for msvp-config

Added comment for msvp-config

--- src/configs/msvp-config.coffee
+++ src/configs/msvp-config.coffee
@@ -26997,6 +26997,8 @@
 	'zeusclicks.com'
 	'zintext.com'
 	'zmedia.com'
+
+#	Added manually in webbooost

 	'cdn.onedmp.com'
 	'globalteaser.ru'
@@ -27011,4 +27013,4 @@
 	'mg.yadro.ru'
 	'c.marketgid.com'
 	'imgg.marketgid.com'
-}
+}


Size calculation regarding ratio is now more accurate.

--- coffee/ruban.coffee
+++ coffee/ruban.coffee
@@ -45,16 +45,31 @@
     if width > height
       min = height
       paddingV = @options.minPadding
+      @$sections.css(
+        'font-size':      "#{min * 0.4}%"
+        'padding-top':    paddingV,
+        'padding-bottom': paddingV
+      )
+      height = @$current.height()
       paddingH = "#{(width - @options.ratio*height)/2}px"
+      @$sections.css(
+        'padding-left':  paddingH
+        'padding-right': paddingH
+      )
     else
       min = width
       paddingH = @options.minPadding
+      @$sections.css(
+        'font-size':      "#{min * 0.4}%"
+        'padding-left':  paddingH,
+        'padding-right': paddingH
+      )
+      width = @$current.width()
       paddingV = "#{(height - width/@options.ratio)/2}px"
-
-    @$sections.css(
-      'font-size': "#{min * 0.4}%"
-      'padding':   "#{paddingV} #{paddingH}"
-    )
+      @$sections.css(
+        'padding-top':    paddingV,
+        'padding-bottom': paddingV
+      )

   checkHash: =>
     hash = window.location.hash


fix routes like /browse/all

fix routes like /browse/all

--- apps/browse/index.coffee
+++ apps/browse/index.coffee
@@ -10,3 +10,4 @@
 app.set 'view engine', 'jade'

 app.get '/browse', routes.index
+app.get '/browse/*', routes.index


colibri-imx6.coffee: Set initial versioning and change to 1
the partition holding config.json

Signed-off-by: Florin Sarbu <3a837a37a2ac9072a088641ec330e0e5d5b86f4e@resin.io>

--- colibri-imx6.coffee
+++ colibri-imx6.coffee
@@ -11,6 +11,7 @@
 ]

 module.exports =
+	version: 1
 	slug: 'colibri-imx6dl'
 	aliases: [ 'colibri-imx6' ]
 	name: 'Colibri iMX6dl'
@@ -48,8 +49,7 @@
 	configuration:
 		config:
 			partition:
-				primary: 4
-				logical: 1
+				primary: 1
 			path: '/config.json'

 	initialization: commonImg.initialization


bug fix

bug fix

--- src/plugins/server/crossdomain.coffee
+++ src/plugins/server/crossdomain.coffee
@@ -1,10 +1,10 @@
 plugin =

-    name: 'help'
+    name: 'crossdomain'

     bootstrap: (callback) ->

-        Bot.Server.app.all '/', (req, res, next) ->
+        Bot.Server.app.use (req, res, next) ->

             res.header 'Access-Control-Allow-Origin', '*'
             res.header 'Access-Control-Allow-Headers', 'X-Requested-With'


Update validateForm so that it doesn't check hidden elements

Used offsetParent as it seems quicker than selecting and detecting the
display property

https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetParent

--- modules/form/main/index.coffee
+++ modules/form/main/index.coffee
@@ -37,27 +37,29 @@
     if form.children[i].nodeName.toLowerCase() is 'div'
       element = form.children[i].children[1]

-      # Deal with standard label/input pairs
-      if element.nodeName.toLowerCase() is 'input' or element.nodeName.toLowerCase() is 'textarea'
-        if not element.checkValidity()
-          type = hx.select(element).attr('type')
-          errors.push {
-            message: getValidationMessage element.validationMessage, type
-            node: element
-            validity: element.validity
-            focused: focusedElement is element
-          }
-      else
-        # Deal with radio groups and other similar structured elements
-        input = hx.select(element).select('input').node()
-        type  = hx.select(element).select('input').attr('type')
-        if input and not input.checkValidity()
-          errors.push {
-            message: getValidationMessage input.validationMessage, type
-            node: element
-            validity: input.validity
-            focused: focusedElement is input
-          }
+      # Don't check the validity of hidden elements
+      if element.offsetParent isnt null
+        # Deal with standard label/input pairs
+        if element.nodeName.toLowerCase() is 'input' or element.nodeName.toLowerCase() is 'textarea'
+          if not element.checkValidity()
+            type = hx.select(element).attr('type')
+            errors.push {
+              message: getValidationMessage element.validationMessage, type
+              node: element
+              validity: element.validity
+              focused: focusedElement is element
+            }
+        else
+          # Deal with radio groups and other similar structured elements
+          input = hx.select(element).select('input').node()
+          type  = hx.select(element).select('input').attr('type')
+          if input and not input.checkValidity()
+            errors.push {
+              message: getValidationMessage input.validationMessage, type
+              node: element
+              validity: input.validity
+              focused: focusedElement is input
+            }

   if options.showMessage and errors.length > 0
     # Show the error for the focused element (if there is one) or the first error in the form
@@ -87,4 +89,4 @@
     errors: errors
   }

-hx.validateForm = validateForm+hx.validateForm = validateForm


Make sure we don't modify the options directly

--- framer/LayerStateMachine.coffee
+++ framer/LayerStateMachine.coffee
@@ -29,15 +29,18 @@
 	@define "states",
 		get: -> @_states

+	switchInstant: (stateName) ->
+		@switchTo(stateName, {instant: true})
+
 	switchTo: (stateName, options={}) ->
-
+
 		# Check if the state exists, if not this is a pretty serious error
 		throw Error "No such state: '#{stateName}'" unless @states[stateName]

 		# Prep the properties and the options. The options come from the state, and can be overriden
 		# with the function arguments here.
 		properties = @states[stateName]
-		options = _.defaults(properties.options, options) if properties.options
+		options = _.defaults({}, options, properties.options) if properties.options

 		stateNameA = @currentName
 		stateNameB = stateName


use correct variable in express's request: params

--- scripts/chat-logger.coffee
+++ scripts/chat-logger.coffee
@@ -203,9 +203,9 @@
   robot.router.get "/#{robot.name}/log/:room/feed", (req, res) ->
     feed = new Feed
       title: 'hubot log'
-      description: "log of room: #{req.param.room}"
-      link: "#{process.env.HUBOT_URL}/#{robot.name}/log/#{req.param.room}/feed"
-    feed.item v for v in list_feed_item req.param.room if chat_data()[req.param.room]
+      description: "log of room: #{req.params.room}"
+      link: "#{process.env.HUBOT_URL}/#{robot.name}/log/#{req.params.room}/feed"
+    feed.item v for v in list_feed_item req.params.room if chat_data()[req.params.room]
     res.type 'application/atom+xml'
     res.send feed.render 'atom-1.0'

@@ -214,7 +214,7 @@
     res.send render_items search_items req.query.q

   robot.router.get "/#{robot.name}/log/:room", (req, res) ->
-    room = req.param.room
+    room = req.params.room
     links = []

     if chat_data()[room]?
@@ -236,24 +236,24 @@

   robot.router.get "/#{robot.name}/log/:room/:year", (req, res) ->
     links = []
-    year = parseInt req.param.year
+    year = parseInt req.params.year
     if chat_data()[room]?[year]?
       links.push "#{idx}" if v for idx,v in chat_data()[room][year]
     send_links res, "#{year}", "/#{robot.name}/log/#{room}/#{year}", links

   robot.router.get "/#{robot.name}/log/:room/:year/:month", (req, res) ->
     links = []
-    year = parseInt req.param.year
-    month = parseInt req.param.month
+    year = parseInt req.params.year
+    month = parseInt req.params.month
     if chat_data()[room]?[year]?[month]?
       links.push "#{idx}" if v for idx,v in chat_data()[room][year][month]
     send_links res, "#{year}/#{month}", "#{robot.name}/log/#{room}/#{year}/#{month}", links

   robot.router.get "/#{robot.name}/log/:room/:year/:month/:date", (req, res) ->
-    room = req.param.room
-    year = req.param.year
-    month = parseInt req.param.month
-    date = parseInt req.param.date
+    room = req.params.room
+    year = req.params.year
+    month = parseInt req.params.month
+    date = parseInt req.params.date
     if chat_data()[room]?[year]?[month]?[date]?
       res.type 'text/html'
       res.send render_items "#{year}/#{month}/#{date}", chat_data()[room]?[year]?[month]?[date]?


I hope now i realy concat this fucking files

--- scripts/coffee/view.coffee
+++ scripts/coffee/view.coffee
@@ -32,6 +32,7 @@
 	clear: ->
 		do cell.deactivate for cell in @cells

+
 # Class for cell DOM
 class ViewCell
 	constructor: (position)->


Update src/scripts/github-commit-link.coffee

Fixed variable name typo.

--- src/scripts/github-commit-link.coffee
+++ src/scripts/github-commit-link.coffee
@@ -35,6 +35,6 @@
           url = commit_obj.url.replace(/api\./,'')
           url = url.replace(/repos\//,'')
           url = url.replace(/commits/,'commit')
-          msg.send "Commit: " + commt_obj.commit.message + " " + url
+          msg.send "Commit: " + commit_obj.commit.message + " " + url
     else
       msg.send "Hey! You need to set HUBOT_GITHUB_REPO and HUBOT_GITHUB_TOKEN before I can link to that commit."


Fix translation

Fix translation

--- def/settings.coffee
+++ def/settings.coffee
@@ -194,7 +194,7 @@
       }
       {
         id: 'core.useProxySettingsWhenCallingApm'
-        title: "Utilise le proxy avec la commande 'apm'"
+        title: "Utilise un proxy avec la commande 'apm'"
         desc: "Utilise les paramètres de proxy quand l'outil 'apm' est utilisé."
       }
       {


Remove bugs field

Remove bugs field

--- lib/Normalize.coffee
+++ lib/Normalize.coffee
@@ -9,7 +9,6 @@
     '_npmOperationalInternal'
     'directories'
     'readme'
-    'bugs'
     '_id'
     '_shasum'
     '_from'


test for array ... with or

test for array ... with or

--- test/contracts.coffee
+++ test/contracts.coffee
@@ -413,3 +413,16 @@
   g = (s) -> s.toString()

   g "foo"
+
+test "array contract ... with or", ->
+  Data = ?([...Num or Str])
+
+  getData :: (Data) -> Num or Str
+  getData = (arr) -> arr[0]
+
+  eq (getData [1,2,3]), 1
+  eq (getData ["foo",2,3]), "foo"
+
+  throws (-> getData [null, "string"])
+  throws (-> getData [{},1,2,3])
+  throws (-> getData {test:[1,2,3]})


Fixed private video links

Fixed private video links

Sory, but I don't know to what the originals were referring to, so I changed them to new ones. :)

--- scripts/triggers.coffee
+++ scripts/triggers.coffee
@@ -37,7 +37,7 @@

   # Fun
   '!no'           : "NOOOOOOOOO! http://www.youtube.com/watch?v=umDr0mPuyQc"
-  '!yep'          : "https://www.youtube.com/watch?v=qMxH7JLVdhc"
+  '!yep'          : "https://www.youtube.com/watch?v=ZZ5LpwO-An4"
   '!xy'           : "It's difficult to discuss a solution without first understanding the problem. Please, explain the problem itself and not the solution that you have in mind. For more info on presenting your problem see !help. Thanks! Also see http://mywiki.wooledge.org/XyProblem"
   '!drama'        : "I just can't take it anymore...."
   '!beta'         : "If you're updating from Beta to October RC, make sure you follow the steps in http://octobercms.com/beta to avoid common issues. This is due to necessary changes to the October core to make things more awesome."
@@ -53,7 +53,7 @@
   '!mindblown'    : "http://www.reactiongifs.com/wp-content/uploads/2011/09/mind_blown.gif"
   '!troll'        : "http://25.media.tumblr.com/tumblr_lxgcxtBVcB1qh01r8o1_500.gif"
   '!pew'          : "https://img.aebian.org/pew.jpg"
-  '!doit'         : "https://www.youtube.com/watch?v=nuHfVn_cfHU"
+  '!doit'         : "https://www.youtube.com/watch?v=UhRXn2NRiWI"
   '!trustnobody'  : "https://img.aebian.org/Duck_Dog.jpg"

 module.exports = (robot) ->


Update Gruntfile

Update Gruntfile

--- server-ce/Gruntfile.coffee
+++ server-ce/Gruntfile.coffee
@@ -22,18 +22,17 @@
 				options:
 		            filter: 'exclude',
 		            tasks: [
-		            	'coffee'
+		            	'concurrent'
 		            	'execute'
 		            	'bunyan'
 		            	'availabletasks'
 		           	]
 		            groups:
-		            	"Compile tasks": [
-		            		"compile:server"
-		            		"compile"
-		            	]
 		            	"Run tasks": [
 		            		"run"
+		            		"run:all"
+		            		"run:web"
+		            		"run:document-updater"
 		            		"default"
 		            	]
 		            	"Misc": [
@@ -46,6 +45,7 @@
 	grunt.registerTask 'run:document-updater', "Run document-updater-sharelatex, the real-time document server", ["bunyan", "execute:document-updater"]

 	grunt.registerTask 'run', "Run all of the sharelatex processes", ['concurrent:all']
+	grunt.registerTask 'run:all', 'run'

 	grunt.registerTask 'default', 'run'


Fix api for no results

Fix api for no results

--- server/api/setupApi.coffee
+++ server/api/setupApi.coffee
@@ -82,7 +82,7 @@
             res[field] = test[field]
         return res

-    if submit.results.tests
+    if submit.results?.tests
         for key, test of submit.results.tests
             submit.results.tests[key] = hideOneTest(test)
     return submit


disable analytics during tests

disable analytics during tests

--- lib/analytics.coffee
+++ lib/analytics.coffee
@@ -11,7 +11,7 @@
 _.mixin(require 'underscore.string')

 module.exports = (options) =>
-  #return if module.exports.getUserAgent()?.indexOf?('PhantomJS') > -1
+  return if module.exports.getUserAgent()?.indexOf?('PhantomJS') > -1
   { @mixpanel, @ga, @location } = options
   @location ?= window?.location
   if sd.GOOGLE_ANALYTICS_ID


use behavior in sprite

use behavior in sprite

--- src/layout/sprite_state.coffee
+++ src/layout/sprite_state.coffee
@@ -4,7 +4,7 @@
 # Responsibility:
 # * display and edit status fields
 # * display language used for this sprite
-# * record and display programs made using those languages
+# * record and display behavior made using those languages
 # * update program display based on current action
 #
 # TODO: should this be recreated OR selected for new turtles
@@ -42,8 +42,7 @@
     (world, args) -> world.send 'do', world.get('value')
   )

-  programs =
-    make.rows 'programs', sprites.programs.map(program_row)
+  behavior = make.rows 'behavior', sprites.behavior.map(program_row)

   make.rows 'sprite_state', [
     status_row


Ignore non object selectors

Ignore non object selectors

--- softremovable.coffee
+++ softremovable.coffee
@@ -75,7 +75,7 @@
       selector =
         _id: selector

-    unless options.removed or selector[removed]?
+    if Match.test(selector, Object) and not (options.removed or selector[removed]?)
       selector = _.clone selector
       selector[removed] =
         $exists: false


change Validators format

change Validators format

--- imports/api/util/validators.coffee
+++ imports/api/util/validators.coffee
@@ -5,32 +5,44 @@

 export Validators =

-	isTagParticipant: (shiftId) ->
-		shift = Shifts.findOne shiftId, fields: tagId: 1
+	user:

-		if !Roles.userIsInRole Meteor.userId(), Permissions.participant, shift.tagId
-			throw new Meteor.Error 'notTagParticipant', ''
+		validId: => Meteor.users.findOne(@value) || 'invalidUser'

-	isAdmin: (projectId) ->
-		if !Roles.userIsInRole Meteor.userId(), Permissions.admin, projectId
-			throw new Meteor.Error 'notAdmin', ''
+	shift:

-	isShiftAdmin: (projectId) ->
-		if !Roles.userIsInRole Meteor.userId(), Permissions.shiftAdmin, projectId
-			throw new Meteor.Error 'notShiftAdmin', ''
+		validId: => Shifts.findOne(@value) || 'invalidShift'

-	custom:
+		isTagParticipant: =>
+			shift = Shifts.findOne @value, fields: tagId: 1
+			'notTagParticipant' if !Roles.userIsInRole Meteor.userId(), Permissions.participant, shift.tagId

-		isUser: => Meteor.users.findOne(@value) || 'invalidUser'
+	week:

-		isShift: => Shifts.findOne(@value) || 'invalidShift'
+		validId: => Weeks.findOne(@value) || 'invalidWeek'

-		isWeek: => Weeks.findOne(@value) || 'invalidWeek'
+	project:

-		isProject: => Projects.findOne(@value) || 'invalidProject'
+		validId: => Projects.findOne(@value) || 'invalidProject'

-		isTag: => Projects.findOne('tags._id': @value) || 'invalidTag'
+		isAdmin: => 'notAdmin' if !Roles.userIsInRole Meteor.userId(), Permissions.admin, @value

-		isTeam: => Projects.findOne('teams._id': @value) || 'invalidTeam'
+		isShiftAdmin: => 'notShiftAdmin' if !Roles.userIsInRole Meteor.userId(), Permissions.shiftAdmin, @value

-		isMeetingPoint: => Projects.findOne('meetings._id': @value) || 'invalidMeetingPoint'
+	tag:
+
+		validId: => Projects.findOne('tags._id': @value) || 'invalidTag'
+
+		isParticipant: => 'notTagParticipant' if !Roles.userIsInRole Meteor.userId(), Permissions.participant, @value
+
+		isAdmin: =>
+			project = Projects.findOne {'tags._id': @value}, fields: _id: 1
+			'notAdmin' if !Roles.userIsInRole Meteor.userId(), Permissions.admin, project._id
+
+	team:
+
+		validId: => Projects.findOne('teams._id': @value) || 'invalidTeam'
+
+	meetingPoint:
+
+		validId: => Projects.findOne('meetings._id': @value) || 'invalidMeetingPoint'


Add root node to POST contributions/

Add root node to POST contributions/

--- src/app/config/restangular.coffee
+++ src/app/config/restangular.coffee
@@ -14,3 +14,13 @@
     if operation is "getList"
       return response.data[what]

+  RestangularProvider.setRequestInterceptor (element, operation, what, url) ->
+    if operation is "post"
+      # Add singular root node to element
+      newElem = {}
+      newElem[what.substring(0, what.length-1)] = element
+      return newElem
+    if operation is "update"
+      # Trim the 's' off so we can reference the singular root note name
+      # This is a total hack and won't work for resources like "people"
+      return response.data[what.substring(0, what.length-1)]


fix(trash/spam): Typo `sent` => `spam`

fix(trash/spam): Typo `sent` => `spam`

--- src/mailbox-perspective.coffee
+++ src/mailbox-perspective.coffee
@@ -276,7 +276,7 @@
     if @isSent()
       query.order(Thread.attributes.lastMessageSentTimestamp.descending())

-    unless @categoriesSharedName() in ['sent', 'trash']
+    unless @categoriesSharedName() in ['spam', 'trash']
       query.where(inAllMail: true)

     if @_categories.length > 1 and @accountIds.length < @_categories.length


Fixes

Fixes

--- server/bootstrap.coffee
+++ server/bootstrap.coffee
@@ -56,7 +56,7 @@
 		# [Location] Porto Alegre, RS
 		Campaigns.insert
 			name: "Pão dos Pobres"
-			user: getUser('Por Pão dos Pobres')
+			user: getUser('Pão dos Pobres')
 			createdAt: new Date
 			mainMedia:
 				type: 'video'
@@ -71,7 +71,7 @@
 		# [Location] Porto Alegre, RS
 		Campaigns.insert
 			name: "Ajude-me a Vencer o Câncer!"
-			user: getUser('Por Leonardo Konarzewski')
+			user: getUser('Leonardo Konarzewski')
 			createdAt: new Date
 			mainMedia:
 				type: 'image'
@@ -85,7 +85,7 @@
 		# [Location] Porto Alegre, RS
 		Campaigns.insert
 			name: "Asilo Padre Cacique"
-			user: getUser('Por Asilo Padre Cacique')
+			user: getUser('Asilo Padre Cacique')
 			createdAt: new Date
 			mainMedia:
 				type: 'image'
@@ -99,7 +99,7 @@
 		# [Location] Curitiba, PR
 		Campaigns.insert
 			name: "Ajude o João"
-			user: getUser('Por Rafaela Mendonça')
+			user: getUser('Rafaela Mendonça')
 			createdAt: new Date
 			mainMedia:
 				type: 'image'
@@ -113,7 +113,7 @@
 		# [Location] São Paulo, SP
 		Campaigns.insert
 			name: "Animais Carentes"
-			user: getUser('Por Felipe Madeira')
+			user: getUser('Felipe Madeira')
 			createdAt: new Date
 			mainMedia:
 				type: 'image'
@@ -127,7 +127,7 @@
 		# [Location] Gravataí, RS
 		Campaigns.insert
 			name: "Doação para Vila Maria"
-			user: getUser('Por Joana Silva')
+			user: getUser('Joana Silva')
 			createdAt: new Date
 			mainMedia:
 				type: 'image'
@@ -197,5 +197,5 @@
 		# 	donatedValue: 212450

-Meteor.startup ->
-	Bootstrap()+# Meteor.startup ->
+# 	Bootstrap()


Revert codemirror changes from other branch

--- src/smc-webapp/editor_codemirror.cjsx
+++ src/smc-webapp/editor_codemirror.cjsx
@@ -66,44 +66,9 @@

 default_store_state =
     style :
-        border     : '1px solid grey'
-        'font-family': 'monospace !important'
+        border : '1px solid grey'
     value : ''
     options : {}
-###
-        mode                      : required
-        read_only                 : false
-        line_numbers              : @props.editor_settings.line_numbers
-        first_line_number         : @props.editor_settings.first_line_number
-        show_trailing_whitespace  : @props.editor_settings.show_trailing_whitespace
-        indent_unit               : @props.editor_settings.indent_unit
-        tab_size                  : @props.editor_settings.tab_size
-        smart_indent              : @props.editor_settings.smart_indent
-        electric_chars            : @props.editor_settings.electric_chars
-        undo_depth                : @props.editor_settings.undo_depth
-        match_brackets            : @props.editor_settings.match_brackets
-        auto_close_brackets       : @props.editor_settings.auto_close_brackets
-        auto_close_xml_tags       : @props.editor_settings.auto_close_xml_tags
-        line_wrapping             : @props.editor_settings.line_wrapping
-        style_active_line         : 15    # @props.editor_settings.style_active_line  # (a number between 0 and 127)
-        spaces_instead_of_tabs    : @props.editor_settings.spaces_instead_of_tabs
-        match_xml_tags            : @props.editor_settings.match_xml_tags
-        code_folding              : @props.editor_settings.code_folding
-        bindings                  : @props.editor_settings.bindings  # 'standard', 'vim', or 'emacs'
-        theme                     : @props.editor_settings.theme
-        track_revisions           : @props.editor_settings.track_revisions
-        delete_trailing_whitespace: editor_settings.strip_trailing_whitespace  # delete on save
-        public_access             : false
-
-        # I'm making the times below very small for now.  If we have to adjust these to reduce load, due to lack
-        # of capacity, then we will.  Or, due to lack of optimization (e.g., for big documents). These parameters
-        # below would break editing a huge file right now, due to slowness of applying a patch to a codemirror editor.
-
-        cursor_interval           : 1000   # minimum time (in ms) between sending cursor position info to hub -- used in sync version
-        sync_interval             : 500    # minimum time (in ms) between synchronizing text with hub. -- used in sync version below
-
-        completions_size          : 20    # for tab completions (when applicable, e.g., for sage sessions)
-###

 exports.init_redux = init_redux = (redux, project_id, filename) ->
     name = redux_name(project_id, filename)
@@ -134,8 +99,6 @@
             style       : rtypes.object
             scroll_info : rtypes.object
             doc         : rtypes.object
-        account :
-            editor_settings  : rtypes.object

     propTypes :
         actions     : rtypes.object
@@ -165,50 +128,6 @@

         @cm.on('change', @_cm_change)
         @cm.on('scroll', @_cm_scroll)
-######
-        options =
-            mode                    : {name:opts.mode, globalVars: true}
-            readOnly                : opts.read_only
-            lineNumbers             : opts.line_numbers
-            firstLineNumber         : opts.first_line_number
-            showTrailingSpace       : opts.show_trailing_whitespace
-            indentUnit              : opts.indent_unit
-            tabSize                 : opts.tab_size
-            smartIndent             : opts.smart_indent
-            electricChars           : opts.electric_chars
-            undoDepth               : opts.undo_depth
-            matchBrackets           : opts.match_brackets
-            autoCloseBrackets       : opts.auto_close_brackets
-            autoCloseTags           : opts.auto_close_xml_tags
-            lineWrapping            : opts.line_wrapping
-            styleActiveLine         : opts.style_active_line
-            indentWithTabs          : not opts.spaces_instead_of_tabs
-            showCursorWhenSelecting : true
-            extraKeys               : extraKeys
-            cursorScrollMargin      : 40
-            autofocus               : false
-
-        if opts.match_xml_tags
-            options.matchTags = {bothTags: true}
-
-        if opts.code_folding
-             extraKeys["Ctrl-Q"] = (cm) -> cm.foldCodeSelectionAware()
-             options.foldGutter  = true
-             options.gutters     = ["CodeMirror-linenumbers", "CodeMirror-foldgutter"]
-
-        if opts.bindings? and opts.bindings != "standard"
-            options.keyMap = opts.bindings
-            #cursorBlinkRate: 1000
-
-        if opts.theme? and opts.theme != "standard"
-            options.theme = opts.theme
-######
-        cm = CodeMirror.fromTextArea(node, options)
-        cm.save = () => @click_save_button()
-
-        if opts.bindings == 'vim'
-            # annoying due to api change in vim mode
-            cm.setOption("vimMode", true)

     _cm_change: ->
         console.log("_cm_change")


User colours sync using system pusher events

--- lib/atom_pair.coffee
+++ lib/atom_pair.coffee
@@ -45,13 +45,12 @@
     _.extend(@, HipChatInvite, Marker, GrammarSync, AtomPairConfig)

   disconnect: ->
-    @pairingChannel.trigger 'client-disconnected', {colour: @markerColour}
     setTimeout((=>
       @pusher.disconnect()
       @editorListeners.dispose() #remove editor event listeners
       )
-    ,500)
-    _.each @friendColours, (colour) => @clearMarkers(colour) #get rid of colours
+    , 500)
+    # _.each @friendColours, (colour) => @clearMarkers(colour) #get rid of colours
     atom.views.getView(@editor).removeAttribute('id')
     @hidePanel()

@@ -70,10 +69,6 @@
       @sessionId = @joinView.miniEditor.getText()
       keys = @sessionId.split("-")
       [@app_key, @app_secret] = [keys[0], keys[1]]
-
-      # takenColour = @colours[keys[3]]
-      # @assignColour(takenColour)
-
       @joinPanel.hide()

       atom.workspace.open().then => @startPairing() #starts a new tab to join pairing session
@@ -99,7 +94,7 @@
     @pairingChannel.unsubscribe()
     @markerColour = @colours[@membersCount]
     @subscribeToPusher()
-    @listenForEvents()
+    @synchronizeColours()

   subscribeToPusher: ->
     @pusher = new Pusher @app_key,
@@ -147,8 +142,8 @@
           @updateCollaboratorMarker(event)
       )

-    @pairingChannel.bind 'client-disconnected', (data) =>
-      @clearMarkers(data.colour)
+    @pairingChannel.bind 'pusher:member_removed', (member) =>
+      @clearMarkers(member.id)
       disconnectView = new AlertView "Your pair buddy has left the session."
       atom.workspace.addModalPanel(item: disconnectView, visible: true)

@@ -165,10 +160,16 @@
     atom.views.getView(@editor).setAttribute('id', 'AtomPair')

     @subscribeToPusher()
-
+    @synchronizeColours()
+
+
+  synchronizeColours: ->
     @pairingChannel.bind 'pusher:subscription_succeeded', (members) =>
       @membersCount = members.count
       return @resubscribe() unless @markerColour
+      colours = Object.keys(members.members)
+      friendColours = _.without(colours, @markerColour)
+      _.each(friendColours, (colour) => @addMarker 0, colour)
       @listenForEvents()

   listenForDestruction: ->