Ru localization (#546)

Ru localization (#546)

* Ru translation for new_home section

* Ru translation for play_level section

* Ru translation for avatar_selection section

* Ru translation for char_customization_modal section

* Ru translation for about section

* Ru translation for different sections

* Ru translation - fixed level names to fit them

* Update ru.coffee

--- app/locale/ru.ozar.coffee
+++ app/locale/ru.ozar.coffee
@@ -45,28 +45,28 @@
     century_skills_blurb1: "Ученики не просто прокачивают своего героя. Они прокачивают самих себя."
     century_skills_quote1: "Ты застрял... так что ты перебираешь все возможные варианты как исправить и пробуешь еще раз. Я не смог оказаться здесь если бы не старался."
     century_skills_subtitle1: "Критическое мышление"
-#    century_skills_subblurb1: "With coding puzzles that are naturally scaffolded into increasingly challenging levels, CodeCombat's programming game ensures kids are always practicing critical thinking."
-#    century_skills_quote2: "Everyone else was making mazes, so I thought, ‘capture the flag’ and that’s what I did."
-#    century_skills_subtitle2: "Creativity"
-#    century_skills_subblurb2: "CodeCombat encourages students to showcase their creativity by building and sharing their own games and webpages."
-#    century_skills_quote3: "If I got stuck on a level. I would work with people around me until we were all able to figure it out."
-#    century_skills_subtitle3: "Collaboration"
-#    century_skills_subblurb3: "Throughout the game, there are opportunities for students to collaborate when they get stuck and to work together using our pair programming guide."
-#    century_skills_quote4: "I’ve always had aspirations of designing video games and learning how to code ... this is giving me a great starting point."
-#    century_skills_subtitle4: "Communication"
-#    century_skills_subblurb4: "Coding requires kids to practice new forms of communication, including communicating with the computer itself and conveying their ideas using the most efficient code."
-#    classroom_in_box_title: "We Strive To:"
-#    classroom_in_box_blurb1: "Engage every student so that they believe coding is for them."
-#    classroom_in_box_blurb2: "Empower any educator to feel confident when teaching coding."
-#    classroom_in_box_blurb3: "Inspire all school leaders to create a world-class computer science program."
-#    click_here: "Click here"
-#    creativity_rigor_title: "Where Creativity Meets Rigor"
-#    creativity_rigor_subtitle1: "Make coding fun and teach real-world skills"
-#    creativity_rigor_blurb1: "Students type real Python and JavaScript while playing games that encourage trial-and-error, critical thinking, and creativity. Students then apply the coding skills they’ve learned by developing their own games and websites in project-based courses."
-#    creativity_rigor_subtitle2: "Reach students at their level"
-#    creativity_rigor_blurb2: "Every CodeCombat level is scaffolded based on millions of data points and optimized to adapt to each learner. Practice levels and hints help students when they get stuck, and challenge levels assess students' learning throughout the game."
-#    creativity_rigor_subtitle3: "Built for all teachers, regardless of experience"
-#    creativity_rigor_blurb3: "CodeCombat’s self-paced, standards-aligned curriculum makes teaching computer science possible for everyone. CodeCombat equips teachers with the training, instructional resources, and dedicated support to feel confident and successful in the classroom."
+    century_skills_quote2: "Все остальные делали лабиринты, поэтому я подумал: 'захват флага', и вот что я сделал."
+    century_skills_subblurb1: "С помощью головоломок по программированию, которые естественным путем выстраиваются в повышающие сложность уровни, наша игра убедит детей использовать критическое мышление."
+    century_skills_subtitle2: "Творчество"
+    century_skills_subblurb2: "CodeCombat вдохновляет учеников делится своим творчеством в ходе построения своих игр и веб страниц."
+    century_skills_quote3: "Если я застрял на уровне. Я буду работать с людьми вокруг меня, пока мы все не сможем понять это."
+    century_skills_subtitle3: "Коллаборация"
+    century_skills_subblurb3: "На протяжении всей игры у студентов есть возможность сотрудничать, когда они застряли, и работать вместе, используя наше руководство по парному программированию."
+    century_skills_quote4: "Я всегда стремился создавать видеоигры и учиться программировать ... это дает мне отличную отправную точку."
+    century_skills_subtitle4: "Коммуникация"
+    century_skills_subblurb4: "Программирование требует от детей практиковать новые формы общения, включая общение с самим компьютером и передачу своих идей с использованием наиболее эффективного кода."
+    classroom_in_box_title: "Мы хотим:"
+    classroom_in_box_blurb1: "Вовлечь каждого ученика, чтобы он поверил, что программирование доступно каждому."
+    classroom_in_box_blurb2: "Дать возможность любому преподавателю чувствовать себя уверенно при обучении программированию."
+    classroom_in_box_blurb3: "Вдохновите всех школьных руководителей создать программу программирования мирового класса."
+    click_here: "Кликните сюда"
+    creativity_rigor_title: "Где творчество встречается со строгостью"
+    creativity_rigor_subtitle1: "Сделайте программирование веселым и обучайтесь реальным навыкам"
+    creativity_rigor_blurb1: "Ученики исполъзуют Python и JavaScript, играя в игры, которые поощряют метод проб и ошибок, критическое мышление и творческий подход. Затем стуученикиденты применяют приобретенные навыки программирования, разрабатывая свои собственные игры и веб-сайты на курсах, основанных на проектах."
+    creativity_rigor_subtitle2: "Подберите уровень для учеников"
+    creativity_rigor_blurb2: "Каждый уровень CodeCombat создается на основе миллионов данных и оптимизируется для адаптации к каждому учащемуся. Уровни практики и советы помогают учащимся, когда они застряли, и уровни сложности оценивают обучение студентов на протяжении всей игры."
+    creativity_rigor_subtitle3: "Создан для всех учителей, независимо от опыта"
+    creativity_rigor_blurb3: "Самостоятельная, стандартизированная учебная программа CodeCombat делает преподавание информатики возможным для всех. CodeCombat предоставляет учителям учебные, учебные ресурсы и специальную поддержку, чтобы чувствовать себя уверенно и успешно в классе."
     featured_partners_title1: "Зарекомендован в"
     featured_partners_title2: "Награды и партнеры"
     featured_partners_blurb1: "Совет колледжа одобрил провайдера"
@@ -76,47 +76,47 @@
     featured_partners_blurb5: "Официальный челн CSforAll"
     featured_partners_blurb6: "Партнер Hour of Code"
     for_leaders_title: "Для руководителей школ"
-#    for_leaders_blurb: "A Comprehensive, Standards-Aligned Computer Science Program"
-#    for_leaders_subtitle1: "Easy Implementation"
-#    for_leaders_subblurb1: "A web-based program that requires no IT support. Get started in under 5 minutes using Google or Clever Single Sign-On (SSO)."
-#    for_leaders_subtitle2: "Full Coding Curriculum"
-#    for_leaders_subblurb2: "A standards-aligned curriculum with instructional resources and professional development to enable any teacher to teach computer science."
-#    for_leaders_subtitle3: "Flexible Use Cases"
-#    for_leaders_subblurb3: "Whether you want to build a Middle School coding elective, a CTE pathway, or an AP Computer Science Principles class, CodeCombat is tailored to suit your needs."
-#    for_leaders_subtitle4: "Real-World Skills"
-#    for_leaders_subblurb4: "Students build grit and develop a growth mindset through coding challenges that prepare them for the 500K+ open computing jobs."
+    for_leaders_blurb: "Комплексная стандартизированная программа по Информатике"
+    for_leaders_subtitle1: "Простое внедрение"
+    for_leaders_subblurb1: "Веб-программа, не требующая поддержки ИТ. Начните менее чем за 5 минут, используя Google или Clever Single Sign-On (SSO)."
+    for_leaders_subtitle2: "Полный учебный план"
+    for_leaders_subblurb2: "Соответствующая стандартам учебная программа с учебными ресурсами и профессиональным развитием, позволяющая любому преподавателю преподавать информатику."
+    for_leaders_subtitle3: "Гибкие варианты использования"
+    for_leaders_subblurb3: "Независимо от того, хотите ли вы создать факультатив по программированию в средней школе, курс CTE или класс AP Computer Science Principles, CodeCombat разработан с учетом ваших потребностей."
+    for_leaders_subtitle4: "Навыки реального мира"
+    for_leaders_subblurb4: "Ученики выстраивают навыки и систему мышления с помощью задач по программированию, которые готовят их к работе на 500K+ открытых позициях."
     for_teachers_title: "Для учителей"
     for_teachers_blurb: "Инструменты для раскрытия потенциала ученика"
-#    for_teachers_subtitle1: "Project-Based Learning"
-#    for_teachers_subblurb1: "Promote creativity, problem-solving, and confidence in project-based courses where students develop their own games and webpages."
-#    for_teachers_subtitle2: "Teacher Dashboard"
-#    for_teachers_subblurb2: "View data on student progress, discover curriculum resources, and access real-time support to empower student learning."
-#    for_teachers_subtitle3: "Built-in Assessments"
-#    for_teachers_subblurb3: "Personalize instruction and ensure students understand core concepts with formative and summative assessments."
-#    for_teachers_subtitle4: "Automatic Differentiation"
-#    for_teachers_subblurb4: "Engage all learners in a diverse classroom with practice levels that adapt to each student's learning needs."
-#    game_based_blurb: "CodeCombat is a game-based computer science program where students type real code and see their characters react in real time."
-#    get_started: "Get started"
-#    global_title: "Join Our Global Community of Learners and Educators"
-#    global_subtitle1: "Learners"
-#    global_subtitle2: "Lines of Code"
-#    global_subtitle3: "Teachers"
-#    global_subtitle4: "Countries"
-#    go_to_my_classes: "Go to my classes"
-#    go_to_my_courses: "Go to my courses"
+    for_teachers_subtitle1: "Проектное обучение"
+    for_teachers_subblurb1: "Способствуйте творчеству, решению проблем и уверенности в проектных курсах, где студенты разрабатывают свои собственные игры и веб-страницы."
+    for_teachers_subtitle2: "Панель управления для учителя"
+    for_teachers_subblurb2: "ледите за прогрессом учеников, просматривайте учебнуе материалы и получите доступ к поддержке в реальном времени, чтобы улучшить учебный процесс."
+    for_teachers_subtitle3: "Встроенные системы оценки"
+    for_teachers_subblurb3: "Персонализируйте подход и убедитесь что ученики понимают ключевые концепты с помощью оценок для уроков и модулей."
+    for_teachers_subtitle4: "Автоматическая дифференциация"
+    for_teachers_subblurb4: "Мотивируйте учеников в разнообразных классах с уровнями, которые можно адаптировать для разных задач."
+    game_based_blurb: "CodeCombat - это игровая программа по информатике, где ученики используют настоящий код и видят как персонажи реагируют на него."
+    get_started: "Начать"
+    global_title: "Присоединяйтесь к нашему глобальному сообществу учеников и учителей"
+    global_subtitle1: "Учащихся"
+    global_subtitle2: "Строк кода"
+    global_subtitle3: "Учителей"
+    global_subtitle4: "Стран"
+    go_to_my_classes: "Перейти в мои классы"
+    go_to_my_courses: "Перейти к моим курсам"
     quotes_quote1: "Назовите любую онлайн программу, я попробовал её. Ни один из них не соответствует CodeCombat. Любой учитель, который хочет, чтобы их ученики научились кодировать ... начните здесь!"
-#    quotes_quote2: " I was surprised about how easy and intuitive CodeCombat makes learning computer science. The scores on the AP exam were much higher than I expected and I believe CodeCombat is the reason why this was the case."
-#    quotes_quote3: "CodeCombat has been the most beneficial for teaching my students real-life coding capabilities. My husband is a software engineer and he has tested out all of my programs. He put this as his top choice."
-#    quotes_quote4: "The feedback … has been so positive that we are structuring a computer science class around CodeCombat. The program really engages the students with a gaming style platform that is entertaining and instructional at the same time. Keep up the good work, CodeCombat!"
+    quotes_quote2: "Я был удивлен, насколько легко и интуитивно понятно CodeCombat делает обучение информатике. Оценки на экзамене AP были намного выше, чем я ожидал, и я считаю, что CodeCombat является причиной, почему это было так."
+    quotes_quote3: "CodeCombat был самым полезным для обучения моих студентов реальным возможностям кодирования. Мой муж - инженер-программист, и он проверил все мои программы. Он назвал это своим лучшим выбором."
+    quotes_quote4: "Отзывы… были настолько позитивными, что мы структурировали урок информатики вокруг CodeCombat. Программа действительно привлекает студентов игровой платформой стиля, которая одновременно интересна и учебна. Продолжайте в том же духе, CodeCombat!"
     see_example: "Посмотреть пример"
     slogan: "Самый захватывающий способ обучения реальному программированию." # {change}
-    teach_cs1_free: "Обучать CS1 Бесплатно"
-#    teachers_love_codecombat_title: "Teachers Love CodeCombat"
-#    teachers_love_codecombat_blurb1: "Report that their students enjoy using CodeCombat to learn how to code"
-#    teachers_love_codecombat_blurb2: "Would recommend CodeCombat to other computer science teachers"
-#    teachers_love_codecombat_blurb3: "Say that CodeCombat helps them support students’ problem solving abilities"
-#    teachers_love_codecombat_subblurb: "In partnership with McREL International, a leader in research-based guidance and evaluations of educational technology."
-#    top_banner_blurb: "New: join our mission!"
+    teach_cs1_free: "Обучать Введению в Информатику бесплатно"
+    teachers_love_codecombat_title: "Учителя любят CodeCombat"
+    teachers_love_codecombat_blurb1: "Сообщите, что их ученики любят использовать CodeCombat, чтобы научиться кодировать"
+    teachers_love_codecombat_blurb2: "Рекомендую CodeCombat всем учителмя информатики"
+    teachers_love_codecombat_blurb3: "Скажите, что CodeCombat помогает им поддерживать способности учащихся решать проблемы"
+    teachers_love_codecombat_subblurb: "В партнерстве с McREL International, лидером в области основанного на исследованиях руководства и оценки образовательных технологий."
+    top_banner_blurb: "Новости: присоединяйтесь к нашей миссии!"
     try_the_game: "Попробуй игру"
     classroom_edition: "Классная комната:"
     learn_to_code: "Учись программировать:"
@@ -252,9 +252,7 @@

   play:
     title: "Играть уровни CodeCombat - Учи Python, JavaScript и HTML"
-    meta_description: "Учи программирование с помощью обучающей игры для начинающих.
-    Учи Python или JavaScript пока решаешь лабиринты, делаешь свои игры и прокачиваешься.
-    Брось вызов своим друзьям в многопользовательских аренах."
+    meta_description: "Учи программирование с помощью обучающей игры для начинающих. Учи Python или JavaScript пока решаешь лабиринты, делаешь свои игры и прокачиваешься. Брось вызов своим друзьям в многопользовательских аренах."
     level_title: "__level__ - Учись программировать на Python, JavaScript, HTML"
     video_title: "__video__ | Видео уровень"
     game_development_title: "__level__ | Разработка игр"
@@ -809,39 +807,39 @@
     view_gallery: "Просмотреть галерею"
     project_published_noty: "Ваш уровень опубликован!"
     keep_editing: "Продолжить редактировать"
-#    learn_new_concepts: "Learn new concepts"
-#    watch_a_video: "Watch a video on __concept_name__"
-#    concept_unlocked: "Concept Unlocked"
-#    use_at_least_one_concept: "Use at least one concept: "
-#    command_bank: "Code Bank"
-#    learning_goals: "Learning Goals"
-#    start: "Start"
-#    vega_character: "Vega Character"
-#    capella_character: "Capella Character"
-#    octans_character: "Octans Character"
-#    click_to_continue: "Click to Continue"
-#    share_your_project: "Share Your Project"
-#    keep_editing_your_project: "You can always keep editing your project, even after sharing it."
-#    copy_url: "Copy 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"
+    learn_new_concepts: "Изучать новую тему"
+    watch_a_video: "Смотреть видео на тему __concept_name__"
+    concept_unlocked: "Тема разблокирована"
+    use_at_least_one_concept: "Используйте хотя бы одну тему: "
+    command_bank: "Справочник"
+    learning_goals: "Учебные цели"
+    start: "Начать"
+    vega_character: "Персонаж Vega"
+    capella_character: "Персонаж Капелла"
+    octans_character: "Персонаж Октанс"
+    click_to_continue: "Кликните чтобы продолжить"
+    share_your_project: "Поделится своим проектом"
+    keep_editing_your_project: "Вы всегда можете редактировать свой проект, даже после того как поделились."
+    copy_url: "Копировать URL"
+    level_status: "Статус"
+    level_status_locked: "Заблокировано"
+    level_status_in_progress: "В работе"
+    level_status_complete: "Завершенно"
+    level_type_practice: "Практика"
+    level_type_challenge: "Испытание"
+    level_type_cutscene: "Видео сцена"
+    level_type_capstone: "Проект"
+    level_type_intro: "Введение"
+    level_type_practice_level: "Практика"
+    level_type_challenge_level: "Испытание"
+    level_type_cutscene_level: "Видеосцена"
+    level_type_capstone_level: "Проекта"
+    level_type_capstone_project: "Завершающий проект"
+    level_type_intro_level: "Введения"
+    content_type_avatar: "Выбор аватара"
+    content_type_cinematic: "Сцена"
+    content_type_interactive: "Проверка темы"
+    feedback: "Обратная связь"

   apis:
     methods: "Методы"
@@ -1050,9 +1048,9 @@
     wallOfDarkness_name: "Стена Тьмы"
     wallOfDarkness_description: "Скройтесь от любопытных взглядов за стеной из теней."

-#  avatar_selection:
-#    pick_an_avatar: "Pick an avatar that will represent you as a player"
-#    select_avatar_title: "Choose Player Avatar"
+  avatar_selection:
+    pick_an_avatar: "Выберите аватар, который будет представлять вас в игре."
+    select_avatar_title: "Выберите игровой аватар"

   premium_features:
     get_premium: "Получи
CodeCombat
Премиум" # Fit into the banner on the /features page
@@ -1113,12 +1111,12 @@
     level_to_unlock: "Разблокируется на уровне:" # Label for which level you have to beat to unlock a particular hero (click a locked hero in the store to see)
     restricted_to_certain_heroes: "Только определённые герои могут играть на этом уровне."

-#  char_customization_modal:
-#    heading: "Customize Your Hero"
-#    body: "Body"
-#    name_label: "Hero's Name"
-#    hair_label: "Hair Color"
-#    skin_label: "Skin Color"
+  char_customization_modal:
+    heading: "Настрой своего героя"
+    body: "Тело"
+    name_label: "Имя героя"
+    hair_label: "Цвет волос"
+    skin_label: "Цвет кожи"

   skill_docs:
     function: "функция" # skill types
@@ -1148,7 +1146,7 @@
     optional_parameters: "Необязательные параметры"
     returns: "Возвращает"
     granted_by: "Предоставлено"
-#    description_coming_soon: "Description coming soon"
+    description_coming_soon: "Описание скоро появится"

   save_load:
     granularity_saved_games: "Сохранено"
@@ -1170,8 +1168,8 @@
     editor_config_behaviors_description: "Автозавершение кавычек, квадратных и фигурных скобок."

   about:
-#    title: "About CodeCombat - Engaging Students, Empowering Teachers, Inspiring Creation"
-#    meta_description: "Our mission is to level computer science through game-based learning and make coding accessible to every learner. We believe programming is magic and want learners to be empowered to to create things from pure imagination."
+    title: "О CodeCombat - Вдохновляем учеников, Помогаем Учителям, Поощряем творчество."
+    meta_description: "Наша задача повысить уровень компьютерной граммотности с помощью игрового обучения и сделать обучение программированию доступным любому учащемуся. Мы верим что программирование это магия и хотим чтобы ученики получили возможность создавать новое с помощью воображения."
     learn_more: "Узнать больше"
     main_title: "Чтобы научиться программировать, необходимо (много) писать код."
     main_description: "Задача CodeCombat в том, чтобы вы проходили обучение с улыбкой на лице."
@@ -1180,31 +1178,31 @@
     story_link: "История"
     press_link: "Для прессы"
     mission_title: "Наша задача: сделать программирование доступным для каждого учащегося на Земле." # {change}
-#    mission_teach: "Teach through Adventure"
-#    mission_desc_1: "In today's world, kids are inundated with entertainment that grows more engaging each year. Every app, every video, every game, every advertisement is created with the goal of capturing and holding their attention. To compete in this environment, education must find ways to hold students' attention and capture their imagination."
-#    mission_desc_2: "We believe that learning computer science can be captivating. It should feel like magic. After all, programming empowers anyone to explore new ideas, to build something from pure imagination, and to interact with each other in new ways. Our goal is to inspire a connection to the creativity and imagination inherent in computer science by building educational games that are creative and imaginative."
-#    mission_desc_3: "Over the last five years, over twelve million people have learned to write Python and JavaScript using CodeCombat, making it one of the most popular coding games in history. Now, we are excited to launch Ozaria and expand on our commitment to inspire a new generation of students to learn computer science. Ozaria is an adventure game that places each learner at the center of a legendary story where they must save the world of Ozaria from impending darkness by mastering the lost magic of coding. As the narrative unfolds, a powerful student-centered learning experience is created - each student’s investment in Ozaria’s story paralleling the investment they are making in their own learning journey."
+    mission_teach: "Обучайте через приключение"
+    mission_desc_1: "В современном мире, дети перенасыщены развлечениями, которые ростут с каждым годом. Каждое приложение, каждое видео, каждая игра, каждая реклама сделана чтобы завладеть и удерживать ваше внимание. Чтобы конкурировать в таком окружении, образование должно найти способ удерживать внимание учеников и давать волю их воображению."
+    mission_desc_2: "Мы верим что обучение информатике может быть увлекательным. Оно должно ощущаться как волшебство. В конце концов, программирование помогает творить новые идей, делать что то новое силой воображения и взаимодействовать новыми способами. Наша цель поддержать связь между творчеством и воображением так свойственным информатике, делая обучающие игры построенные на творчестве и воображении."
+    mission_desc_3: "За последние 5 лет более 20 миллионов человек обучались программировать на Python и Javascript с помощью CodeCombat, тем самым превратив ее в одну из самых популярных игр по обучению программированию в истории. Теперь мы с гордостью запускаем Ozaria и расширяем наш охват по вдохновлению нового поколения учеников к программированию. Ozaria - это приключенческая игра, которая помещает каждого учащегося в круговорот легендарной истории где они спасут сказочный мир  от надвигающейся тьмы при помощи утерянной магии программирования. По мере того как сюжет раскрывается, ученики будут в центре истории параллельно обучаясь. Каждый вклад ученика в историю Озарии будет их вкладом в их обучение."
     team_title: "Присоединяйтесь к команде CodeCombat"
     team_values: "Мы ценим открытый и вежливый диалог, где побеждают лучшие идеи. Наши решения основаны на исследовании пожеланий клиентов, а наш процесс направлен на то, чтобы приносить осязаемые результаты. У нас все при деле, от CEO до контрибьютеров на GitHub, потому что мы ценим рост и обучение в своей команде." # {change}
     nick_title: "Сооснователь, CEO"
     matt_title: "Сооснователь, CTO"
-#    lawrence_title: "Customer Success Manager"
-#    shan_title: "Head of Marketing, CodeCombat Greater China"
-#    run_title: "Head of Operations, CodeCombat Greater China"
-#    lance_title: "Head of Technology, CodeCombat Greater China"
-#    zhiran_title: "Head of Curriculum, CodeCombat Greater China"
-#    yuqiang_title: "Head of Innovation, CodeCombat Greater China"
-#    matias_title: "Senior Software Engineer"
-#    ryan_title: "Customer Support Specialist"
-#    maya_title: "Senior Curriculum Developer"
-#    bill_title: "General Manager, CodeCombat Greater China"
-#    shasha_title: "Product and Visual Designer"
-#    daniela_title: "Marketing Manager"
-#    bobby_title: "Game Designer"
-#    brian_title: "Senior Game Design Manager"
-#    stephanie_title: "Customer Support Specialist"
-#    rob_title: "Sales Development Representative"
-#    shubhangi_title: "Senior Software Engineer"
+    lawrence_title: "Менеджер по работе с клиентами"
+    shan_title: "Директор по маркетингу, CodeCombat Китай"
+    run_title: "Операционный директор, CodeCombat Китай"
+    lance_title: "Тех директор, CodeCombat Китай"
+    zhiran_title: "Директор по обучению, CodeCombat Китай"
+    yuqiang_title: "Директор по инновациям, CodeCombat Китай"
+    matias_title: "Старший инженер программист"
+    ryan_title: "Специалист по работе с клиентами"
+    maya_title: "Старший Методист"
+    bill_title: "Директор филиала, CodeCombat Китай"
+    shasha_title: "Продакт дизайнер"
+    daniela_title: "Менеджер по маркетингу"
+    bobby_title: "Игровой дизайнер"
+    brian_title: "Старшйи менеджер по игровому дизайну"
+    stephanie_title: "Специалист поддержки клиентов"
+    rob_title: "Представитель по развитию продаж"
+    shubhangi_title: "Старший инженер программист"
     retrostyle_title: "Иллюстрации"
     retrostyle_blurb: "RetroStyle Games"
     community_title: "...и наше open-source сообщество"
@@ -1243,7 +1241,7 @@
     jobs_custom_contact_2: "представляя нам себя, у вас есть шанс войти с нами в контакт в будущем!"
     contact_title: "Для прессы и контакты"
     contact_subtitle: "Нужно больше информации? Свяжитесь с нами по"
-#    press_subtitle: "or visit our press site at"
+    press_subtitle: "или перейдите на информацию для СМИ"
     screenshots_title: "Скриншоты игры"
     screenshots_hint: "(нажмите для просмотра в полном размере)"
     downloads_title: "Загрузить дополнения и информацию"
@@ -1345,7 +1343,7 @@
     role_default: "Выберите роль"
     primary_role_default: "Выберите главную роль"
     purchaser_role_default: "Выберите роль покупателя"
-#    teacher: "Teacher"
+    teacher: "Учитель"
     tech_coordinator: "Технологический координатор"
     advisor: "Специалист по учебной программе/Советник"
     principal: "Глава"
@@ -1362,7 +1360,7 @@
     organization_label: "Школа"
     school_name: "Название школы"
     city: "Город"
-    state: "Область" # {change}
+    state: "Регион" # {change}
     country: "Страна"
     num_students_help: "Сколько учеников вы ожидаете привлечь в CodeCombat?" # {change}
     num_students_default: "Выберите диапазон"
@@ -1375,12 +1373,12 @@
     college_plus: "Колледж/университет или выше"
     referrer: "Как вы о нас узнали?"
     referrer_help: "Например: от другого учителя, ваших учеников, на конференции, Code.org и т.д."
-    referrer_default: "Выбери Один"
-#    referrer_conference: "Conference (e.g. ISTE)"
+    referrer_default: "Выберите из"
+    referrer_conference: "Конференция"
     referrer_hoc: "Code.org/Hour of Code"
-    referrer_teacher: "Учитель"
-    referrer_admin: "Администратор"
-    referrer_student: "Студент"
+    referrer_teacher: "От учителя"
+    referrer_admin: "От руководителя"
+    referrer_student: "От учеников"
     referrer_pd: "Профессиональные тренинги/семинары"
     referrer_web: "Google"
     referrer_other: "Другое"
@@ -1399,7 +1397,7 @@
     create_account_subtitle: "Получите доступ к инструментам, доступным только учителям, для использования CodeCombat в класной комнате. Подготовьте класс, добавьте ваших учеников и наблюдайте за их прогрессом!"
     convert_account_title: "Улучшить до аккаунта учителя"
     not: "Нет"
-#    full_name_required: "First and last name required"
+    full_name_required: "Необходимо имя и фамилия"

   versions:
     save_version_title: "Сохранить новую версию"
@@ -1513,9 +1511,9 @@
     contribute_to_the_project: "Сотрудничайте с проектом"

   clans:
-#    title: "Join CodeCombat Clans - Learn to Code in Python, JavaScript, and HTML"
-#    clan_title: "__clan__ - Join CodeCombat Clans and Learn to Code"
-#    meta_description: "Join a Clan or build your own community of coders. Play multiplayer arena levels and level up your hero and your coding skills."
+    title: "Присоединяйтесь к кланам CodeCombat - Учите Python, JavaScript, и HTML"
+    clan_title: "__clan__ - Присоединяйтесь к кланам CodeCombat и учитесь программированию."
+    meta_description: "Присоеденитесь к клану или постройте свое сообщество. Играйте многопользовательское уровни и прокачивайте сцой уровень вместе с навыками программирования"
     clan: "Клан"
     clans: "Кланы"
     new_name: "Имя нового клана"
@@ -1576,9 +1574,9 @@

   courses:
     create_new_class: "Создать новый класс"
-#    hoc_blurb1: "Try the"
-#    hoc_blurb2: "Code, Play, Share"
-#    hoc_blurb3: "activity! Construct four different minigames to learn the basics of game development, then make your own!"
+    hoc_blurb1: "Попробовать"
+    hoc_blurb2: "Пиши, Играй, Делись"
+    hoc_blurb3: "активность! Построй 4 разные мини-игры чтобы научится основам игровой разработки, а затем сделай свою игру"
     solutions_require_licenses: "Решения уровней доступны для учителей, имеющих лицензии."
     unnamed_class: "Безымянный класс"
     edit_settings1: "Изменить настройки класса"
@@ -1632,16 +1630,16 @@
     welcome_to_page: "Моя панель ученика" # {change}
     my_classes: "Мои классы"
     class_added: "Класс успешно добавлен!"
-#    view_map: "view map"
-#    view_videos: "view videos"
+    view_map: "перейти к карте"
+    view_videos: "смотреть видео"
     view_project_gallery: "посмотреть проекты моих одноклассников"
     join_class: "Присоединиться к классу" # {change}
     join_class_2: "Присоединиться к классу"
     ask_teacher_for_code: "Спросите у вашего учителя код класса для CodeCombat! Введите его ниже:" # {change}
-    enter_c_code: "<Введите Код Класса>" # {change}
+    enter_c_code: "Введите Код Класса" # {change}
     join: "Присоединиться"
     joining: "Присоединение к классу"
-#    unit_complete: "Unit Complete"
+    unit_complete: "Блок завершен"
     play_arena: "Играть Арену"
     view_project: "Посмотреть проект"
     start: "Старт"


map loads instantly now, closes #62

map loads instantly now, closes #62

--- src/coffee/controllers/player/player-map-ctrl.coffee
+++ src/coffee/controllers/player/player-map-ctrl.coffee
@@ -128,11 +128,12 @@
     CurrentMap.observe().then null, null, ->
       $scope.initializeMap()

-    $scope.initializeMap()
-
     Player.observe().then null, null, ->
       $scope.drawMap()

-    $scope.drawMap()
+    $timeout ->
+      $scope.initializeMap()
+      $scope.drawMap()
+    , 0

 ]


Add helper for markdown extension to fs

Add helper for markdown extension to fs

--- src/markdown-preview.coffee
+++ src/markdown-preview.coffee
@@ -70,5 +70,4 @@
     @markdownBody.show()

   isMarkdownFile: (path) ->
-    extension = fs.extension(path)
-    extension is '.md' or extension is '.markdown'
+    fs.isMarkdownExtension(fs.extension(path))


Support for styles hash format in element creation options

--- js/core/elem/elem.coffee
+++ js/core/elem/elem.coffee
@@ -685,10 +685,12 @@
     _elem = document.createElement( _tagName )
     _id = @_add( _elem )
     @_initCache( _id )
-    unless _options == undefined
+    if _options?
       if _options.attrs
         for _attr in _options.attrs
           @setAttr( _id, _attr[0], _attr[1], true )
+      if _options.styles
+        @setStyles( _id, _options.styles )
     @_elements[_targetId].appendChild(_elem)
     _id


Link to Flight Manual tutorial

Link to Flight Manual tutorial

--- templates/language/grammars/__package-name__.cson
+++ templates/language/grammars/__package-name__.cson
@@ -1,8 +1,8 @@
 # If this is your first time writing a language grammar, check out:
-#   - http://manual.macromates.com/en/language_grammars
+#   - http://flight-manual.atom.io/hacking-atom/sections/creating-a-grammar/

 'scopeName': 'source.__package-name__'
-'name': 'PackageName'
+'name': '__PackageName__'
 'fileTypes': [
   'extension'
 ]


Update examples/pizza.coffee.

Update examples/pizza.coffee.

Guess the DOM has changed a little. No more class .address but an (incorrectly used) <address> tag instead. (same pull request as the vanilla JS example)

http://code.google.com/p/phantomjs/issues/detail?id=921

--- examples/pizza.coffee
+++ examples/pizza.coffee
@@ -10,7 +10,7 @@
     else
       results = page.evaluate ->
         pizza = []
-        list = document.querySelectorAll 'span.address'
+        list = document.querySelectorAll 'address'
         for item in list
           pizza.push(item.innerText)
         return pizza


Use $http instead of $.ajax

Use $http instead of $.ajax

--- app/scripts/mapctrl.coffee
+++ app/scripts/mapctrl.coffee
@@ -72,28 +72,19 @@
       ### HELPERS ###
       ###############
       getTopoJsonLayer = (url, featureName, doClick) ->
-        deferred = $q.defer()
-        $.ajax({
-          url
-          dataType: 'json'
-          success: (response) ->
-            features = topojson.feature(response, response.objects[featureName]).features
-            geojson = L.geoJson(features, {
-              style
-              onEachFeature: (feature, layer) ->
-                layer.on
-                  mouseover: mouseOver
-                  mouseout: (e) ->
-                    geojson.resetStyle(e.target)
-                    $scope.$apply (scope) ->
-                      scope.hoverText = ""
-                  click: onClick if doClick
-            })
-            deferred.resolve geojson
-          error: (response) ->
-            deferred.reject response
-        })
-        deferred.promise
+        $http.get(url).then (response) ->
+          features = topojson.feature(response.data, response.data.objects[featureName]).features
+          geojson = L.geoJson(features, {
+            style
+            onEachFeature: (feature, layer) ->
+              layer.on
+                mouseover: mouseOver
+                mouseout: (e) ->
+                  geojson.resetStyle(e.target)
+                  $scope.$apply (scope) ->
+                    scope.hoverText = ""
+                click: onClick if doClick
+          })

       makeLegend = (map) ->
         legend = L.control(


mailbox model: debug + delete related functions

--- app/models/mailbox.coffee
+++ app/models/mailbox.coffee
@@ -33,11 +33,20 @@
 # Destroy helpers

 Mailbox::destroyMails = (callback) ->
-    Mail.requestDestroy "mailbox", key: @id, callback
+    Mail.requestDestroy "bymailbox", key: @id, callback
+
+Mailbox::destroyMailsToBe = (callback) ->
+    params =
+        startkey: [@id]
+        endkey: [@id + "0"]
+    MailToBe.requestDestroy "bymailbox", params, callback
+
+Mailbox::destroyAttachments = (callback) ->
+    Attachment.requestDestroy "bymailbox", key: @id, callback

 # Just to be able to recognise the mailbox in the console
 Mailbox::toString = ->
-    "[Mailbox " + @name + " #" + @id + "]"
+    "[Mailbox #{@name} #{@id}]"

 Mailbox::fetchFinished = (callback) ->
     @updateAttributes IMAP_last_fetched_date: new Date(), (error) =>
@@ -244,11 +253,11 @@
                         return callback(err) if err

                         if typeof mailToBe is "string"
-                            callback mail
+                            callback null, mail
                         else
                             mailToBe.destroy (error) ->
                                 return callback(err) if err
-                                callback mail
+                                callback null, mail

         message.on "data", (data) ->
             # on data, we feed the parser
@@ -315,14 +324,14 @@
                                 mailsDone++
                                 job.progress mailsDone, results.length

-                                if mailsToGo is mailsDone
+                                if mailsDone is results.length
                                     callback()
                                 else
                                     fetchOne(server, i + 1, results, mailsDone)

         else
             server.logout ->
-                if mailsToGo isnt mailsDone
+                if mailsDone isnt results.length
                     msg = "Could not import all the mail. Retry"
                     server.emit "error", new Error(msg)

@@ -351,7 +360,7 @@
             console.log error
             server.emit "error", error if server?

-    loadInboxMails = (server) ->
+    loadInboxMails = (server) =>
         server.search ['ALL'], (err, results) =>
             if err
                 emitOnErr server, err
@@ -363,7 +372,8 @@
                     server.logout()
                     callback()
                 else
-                    console.log "[" + results.length + "] mails to download"
+                    console.log "[#{@name}] #{results.length} mails to download"
+                    console.log "[#{@name}] Start grabing mail ids"
                     fetchOne server, results, 0, 0, results.length, 0

@@ -382,7 +392,7 @@
                 if error
                     server.logout -> server.emit "error", error
                 else
-                    console.log "#{mailToBe.remoteId} id saved successfully"
+                    #console.log "#{mailToBe.remoteId} id saved successfully"
                     mailsDone++

                     if mailsDone is mailsToGo
@@ -394,8 +404,9 @@
                             activated: true
                             importing: true

-                        @updateAttributes data, (err) ->
-                            server.logout () ->
+                        @updateAttributes data, (err) =>
+                            server.logout () =>
+                                console.log "[#{@name}] All mail ids collected"
                                 callback err
                     else
                         fetchOne server, results, i + 1, mailsDone, mailsToGo, maxId
@@ -415,10 +426,10 @@
 Mailbox::doImport = (job, callback) ->

     emitOnErr = (server, error) ->
-        if error
+        if error and server?
             server.logout () ->
                 console.log error
-                server.emit "error", error if server?
+                server.emit "error", error

     @connectImapServer (err, server) =>
         return emitOnErr server, err if err


add function to mark a mail read remotely

--- app/models/mailbox.coffee
+++ app/models/mailbox.coffee
@@ -201,11 +201,29 @@
         @log 'No host defined'
         callback new Error 'No host defined'

-Mailbox::loadInbox = (server, callback) ->
-    server.openBox 'INBOX', false, (err, box) =>
-        @log "INBOX opened successfully"
-        callback err, server
-
+
+Mailbox::openInbox = (callback) ->
+    @connectImapServer (err, server) =>
+        if err
+            server.emit "error", err if server?
+            callback err
+        else
+            server.openBox 'INBOX', false, (err, box) =>
+                @log "INBOX opened successfully"
+                callback err, server
+
+
+Mailbox::closeBox = (server, callback) ->
+   server.closeBox (err) =>
+        if err
+            @log "cant close box"
+            callback err
+        else
+            server.logout =>
+                @log "logged out from IMAP server"
+                callback()
+
+
 Mailbox::fetchMessage = (server, mailToBe, callback) ->

     if typeof mailToBe is "string"
@@ -276,6 +294,32 @@
             messageFlags = message.flags
             do parser.end

+Mailbox::fetchLastChanges = (server, callback) ->
+    @log "fetch last modification started."
+    callback()
+    #server.fetch("1:#{@ImapLastFetchedId} FLAGS", { struct: false },
+        #headers: 'from'
+        #body: true
+        #cb: (fetch) ->
+            #fetch.on 'message', (msg) ->
+              #console.log('Saw message no. ' + msg.seqno)
+              #body = ''
+              #msg.on 'headers', (hdrs) ->
+                #console.log('Headers for no. ' + msg.seqno + ': ' + show(hdrs))
+              #msg.on 'data', (chunk) ->
+                #body += chunk.toString('utf8')
+              #msg.on 'end', ->
+                #console.log('Finished message no. ' + msg.seqno)
+                #console.log('UID: ' + msg.uid)
+                #console.log('Flags: ' + msg.flags)
+                #console.log('Date: ' + msg.date)
+                #console.log('Body: ' + show(body))
+    #, (err) ->
+        #@log "fetch modification finished."
+        #console.log err if err
+
+        #callback err
+    #)

 Mailbox::getNewMail = (job, callback, limit=250) ->

@@ -292,13 +336,13 @@
             server.emit "error", err if server?
             callback err

-    @connectImapServer (err, server) =>
-        return emitOnErr(server, err) if err
-        @loadInbox server, (err) =>
-            return emitOnErr server, err if err
-            loadNewMails(server, id)
+    @openInbox (err, server) =>
+        return emitOnErr server, err if err
+        loadNewMails server, id, =>
+            @log "New Mails fetched"
+            @fetchLastChanges(server, callback)

-    loadNewMails = (server, id) =>
+    loadNewMails = (server, id, localCallback) =>
         range = "#{id}:#{id + limit}"
         server.search [['UID', range]], (err, results) =>
             return emitOnErr(server, err) if err
@@ -306,7 +350,7 @@
             unless results.length
                 @log "Nothing to download"
                 server.logout (err) ->
-                    callback err
+                    localCallback err
             else
                 @log "#{results.length} mails to download"
                 LogMessage.createImportInfo results, @, ->
@@ -333,7 +377,7 @@
                                 job.progress mailsDone, results.length

                                 if mailsDone is results.length
-                                    callback()
+                                    localCallback()
                                 else
                                     fetchOne(server, i + 1, results, mailsDone)

@@ -342,7 +386,7 @@
                 if mailsDone isnt results.length
                     msg = "Could not import all the mail. Retry"
                     server.emit "error", new Error(msg)
-            callback()
+            localCallback()

 ###
@@ -354,11 +398,9 @@
     # global vars
     mailbox = @

-    @connectImapServer (err, server) =>
+    @openInbox (err, server) ->
         return emitOnErr server, err if err
-        @loadInbox server, (err) ->
-            return emitOnErr server, err if err
-            loadInboxMails server
+        loadInboxMails server

     emitOnErr = (server, err) ->
         if err
@@ -437,8 +479,7 @@
                 console.log err
                 server.emit "error", err

-    @connectImapServer (err, server) =>
-        return emitOnErr server, err if err
+    @openInbox (err, server)  =>
         MailToBe.fromMailbox @, (err, mailsToBe) =>
             if err
                 emitOnErr server, err
@@ -447,8 +488,7 @@
                 server.logout()
                 callback()
             else
-                @loadInbox server, =>
-                    fetchMails server, mailsToBe, 0, mailsToBe.length, 0
+                fetchMails server, mailsToBe, 0, mailsToBe.length, 0

     fetchMails = (server, mailsToBe, i, mailsToGo, mailsDone) =>
         @log "Import progress:  #{i}/#{mailsToBe.length}"
@@ -478,3 +518,19 @@
                     msg = "The box was not fully imported."
                     server.emit 'error', new Error msg
                 callback()
+
+Mailbox::markMailAsRead = (mail, callback) ->
+    @log "Add read flag to mail #{mail.idRemoteMailbox}"
+    @openInbox (err, server) =>
+        if err
+            console.log err if err
+            @closeBox ->
+                callback err
+        else
+            server.addFlags mail.idRemoteMailbox, 'Seen', (err) =>
+                if err
+                    @log "mail #{mail.idRemoteMailbox} not marked as seen"
+                    console.log err
+                else
+                    @log "mail #{mail.idRemoteMailbox} marked as seen"
+                @closeBox server, callback


fix bug in sidebar

fix bug in sidebar

--- app/scripts/widgets.coffee
+++ app/scripts/widgets.coffee
@@ -107,7 +107,7 @@
         structItems = []
         posItems = []
         for key, attrs of corpus_attrs
-            output = @renderItem(key, null, attrs, wordData, sentenceData, tokens).get?(0)
+            output = @renderItem(key, "not_used", attrs, wordData, sentenceData, tokens).get?(0)
             if attrs.customType == "struct"
                 structItems.push [key, output]
             else if attrs.customType == "pos"


composite now removes it's subviews

composite now removes it's subviews

--- src/mixins/composite.coffee
+++ src/mixins/composite.coffee
@@ -21,6 +21,9 @@
         @compost[selector] = view
         view.render()

+  remove: () ->
+    for selector, view of @compost
+      view.remove()

 module.exports =
   mixins:


JoinMessage が外部からアクセスできなかったので修正。

JoinMessage が外部からアクセスできなかったので修正。

--- index.coffee
+++ index.coffee
@@ -4,7 +4,7 @@
 Adapter                                                              = require './src/adapter'
 Response                                                             = require './src/response'
 {Listener,TextListener}                                              = require './src/listener'
-{Message,TextMessage,EnterMessage,LeaveMessage,TopicMessage,CatchAllMessage} = require './src/message'
+{Message,TextMessage,EnterMessage,LeaveMessage,JoinMessage,TopicMessage,CatchAllMessage} = require './src/message'

 module.exports = {
   User
@@ -18,6 +18,7 @@
   TextMessage
   EnterMessage
   LeaveMessage
+  JoinMessage
   TopicMessage
   CatchAllMessage
 }


Move notCapable() check into voxel-plugins loader for voxel-engine

Requires deathcap/voxel-engine@ade4cbcf3a076da80648ca0f7e4680b0fbd134fb

--- index.coffee
+++ index.coffee
@@ -42,6 +42,8 @@

   configuration =
     'voxel-engine':
+      appendDocument: true
+      exposeGlobal: true  # for debugging
       lightsDisabled: true
       arrayType: Uint16Array
       useAtlas: false
@@ -120,12 +122,7 @@

   plugins.loadAll()

-  # the game view element itself
   game = plugins.get('voxel-engine')
-  window.game = window.g = game # for debugging
-  game.appendTo document.body
-  return game if game.notCapable()
-

   # load textures after all plugins loaded (since they may add their own)
   registry = plugins.get('voxel-registry')


router and middleware

router and middleware

--- index.coffee
+++ index.coffee
@@ -1,31 +1,75 @@
 jsBeautify = (require 'js-beautify').js_beautify
 express = require 'express'
+fs = require 'fs'
 _ = require 'underscore'
+
+formatPathRegex = (regex, base = '') ->
+  format = ->
+    regex.toString()
+    .replace /^\/\^/, ''
+    .replace /\/i?$/, ''
+    .replace /\\/g, ''
+    .replace '/?(?=/|$)', ''
+    .replace /\/$/, ''
+
+  if regex
+    path = base + format regex
+
+    if path
+      return path
+    else
+      return '/'
+  else
+    return '/'
+
+funcSource = (func) ->
+  return jsBeautify func.toString()

 module.exports = (options = {}) ->
   {port, ip} = options

   app_info =
     empty: true
+    package: JSON.parse fs.readFileSync('./package.json').toString()
     settings: {}
-    global_middleware: []
-    router_description: []
-    api_description: []
+    middleware: [
+      name: 'serveStatic'
+      source: ''
+    ]
+    router: [
+      path: '/account/login'
+      method: 'GET'
+    ]

   reflectApp = (app) ->
-    global_middleware = _.filter app._router.stack, (layer) ->
-      return layer.regexp.test '/'
+    middlewares = []
+    routers = []

-    global_middleware = _.map global_middleware, (layer) ->
-      return {
-        name: layer.name
-        source: jsBeautify layer.handle.toString()
-      }
+    reflectRouter = (router, base = '') ->
+      base = '' if base == '/'
+
+      for layer in router.stack
+        if layer.route
+          for route_layer in layer.route.stack
+            routers.push
+              path: base + layer.route.path
+              method: route_layer.method.toUpperCase()
+              source: funcSource route_layer.handle
+        else if layer.handle.stack
+          reflectRouter layer.handle, formatPathRegex(layer.regexp)
+        else
+          middlewares.push
+            path: formatPathRegex layer.regexp, base
+            name: layer.name
+            source: funcSource layer.handle
+
+    reflectRouter app._router

     _.extend app_info,
       empty: false
       settings: app.settings
-      global_middleware: global_middleware
+      middleware: middlewares
+      router: routers

   createExplorerServer = ->
     explorer = express()


fix, bump

fix, bump

--- index.coffee
+++ index.coffee
@@ -17,20 +17,20 @@
     console.log('initialising tflobby script')
     console.log('------------------------------------------')

-    if (lobby = @brain.get('tflobby.lobby'))?
+    if (lobby = robot.brain.get('tflobby.lobby'))?
       console.log('attempting to resurrect current!!')
       resurrected = resurrect(lobby)
       console.log(resurrected)
-      @brain.set('tflobby.lobby', resurrected)
+      robot.brain.set('tflobby.lobby', resurrected)

-    if (previous = @brain.get('tflobby.previous'))?
+    if (previous = robot.brain.get('tflobby.previous'))?
       console.log('attempting to resurrect previous!!')
       resurrected = resurrect(previous)
       console.log(resurrected)
-      @brain.set('tflobby.previous', resurrected)
+      robot.brain.set('tflobby.previous', resurrected)

     # chat.flava
-    @brain.set(
+    robot.brain.set(
       'tflobby.chat.flava',
       [
         'you can be my wingman any time.',
@@ -39,7 +39,7 @@
     )

     # chat.affirmative
-    @brain.set(
+    robot.brain.set(
       'tflobby.chat.affirmative',
       [
         'yeah,',
@@ -49,7 +49,7 @@
     )

     # chat.mistake
-    @brain.set(
+    robot.brain.set(
       'tflobby.chat.mistake',
       [
         'um,',
@@ -125,12 +125,12 @@
       mapsByMode[mode] = [] unless mapsByMode[mode]?
       mapsByMode[mode].push(map)

-    @brain.set('tflobby.maps.all', maps)
-    @brain.set("tflobby.maps.#{mode}", maps) for mode, maps of mapsByMode
-    @brain.set('tflobby.maps.popular', popularMaps)
-    @brain.set('tflobby.servers.all', servers)
-    @brain.set('tflobby.servers.names', Object.keys(servers))
-    @brain.set('tflobby.servers.default', defaultServer)
+    robot.brain.set('tflobby.maps.all', maps)
+    robot.brain.set("tflobby.maps.#{mode}", maps) for mode, maps of mapsByMode
+    robot.brain.set('tflobby.maps.popular', popularMaps)
+    robot.brain.set('tflobby.servers.all', servers)
+    robot.brain.set('tflobby.servers.names', Object.keys(servers))
+    robot.brain.set('tflobby.servers.default', defaultServer)

     path = Path.resolve(__dirname, 'src')

@@ -140,6 +140,6 @@
         console.error("#{path} don't exist")
         process.exit(1)

-      @loadFile(path, 'index.coffee')
-      @parseHelp(Path.join(path, 'index.coffee'))
-  ).bind(robot), 5000)
+      robot.loadFile(path, 'index.coffee')
+      robot.parseHelp(Path.join(path, 'index.coffee'))
+  ).bind(@), 5000)


can set mark on server side

can set mark on server side

--- index.coffee
+++ index.coffee
@@ -13,11 +13,7 @@
   app.use '/bower_components', (express.static __dirname + '/bower_components')
   app.use bodyParser.json()

-  router = express.Router()
-
-  # app.use '/', router
-
-  app.resource 'quest',
+  quest = app.resource 'quest',
     load: (id, next) ->
       client.query 'select id, name from quest where id = $1', [id], (err, result) ->
         if err
@@ -37,9 +33,16 @@
     show: (req, res) ->
       res.send req.quest

-  router.post '/mark/:quest_id', (request, response) ->
-    client.query 'insert into daily_mark (quest,date) values ($1,localtimestamp)', [request.quest_id]
-    response.send "ok"
+  mark = app.resource 'mark',
+    index: (req, res) ->
+      console.log req
+      client.query 'select id, date from daily_mark where quest = $1 order by date', [req.quest.id], (err, pgres) ->
+        res.send pgres.rows
+    create: (req, res) ->
+      client.query 'insert into daily_mark (quest,date) values ($1,localtimestamp) returning id, date', [req.quest.id], (err, pgres) ->
+        res.send pgres.rows[0]
+
+  quest.add mark

   app.listen (app.get 'port'), () ->
     console.log "started; port = " + app.get('port')


allow to use robot.send() with channel name

--- src/matteruser.coffee
+++ src/matteruser.coffee
@@ -96,7 +96,8 @@

         # If it's not, continue as normal
         unless user
-            @client.postMessage(str, envelope.room) for str in strings
+            channel = @client.findChannelByName(envelope.room)
+            @client.postMessage(str, channel?.id or envelope.room) for str in strings
             return

         # If it is, we assume they want to DM that user


Add eventbrite to config example

Add eventbrite to config example

--- config.coffee-dist.coffee
+++ config.coffee-dist.coffee
@@ -4,4 +4,8 @@
 	secretKey: null

 # Meetup config
-exports.meetup = null # API key as string+exports.meetup = null # API key as string
+
+#Eventbrite config
+exports.eventbrite =
+	appKey: null


Reattach volatile attachments after Baton Pass.

--- server/attachment.coffee
+++ server/attachment.coffee
@@ -1168,6 +1168,7 @@
     # Nasty stitching of attachments to the recipient.
     for attachment in @attachments
       attachment.pokemon = pokemon
+      attachment.attached = true
       pokemon.attachments.attachments.push(attachment)
     pokemon.boost(@stages)
     @team.unattach(@constructor)


Account: profile details section rewritten

--- client/Account/views/username.coffee
+++ client/Account/views/username.coffee
@@ -1,153 +1,163 @@
-class AccountEditUsername extends KDView
+class AccountEditUsername extends JView
+
+  notify  = (msg)->
+    new KDNotificationView
+      title    : msg
+      duration : 2000
+
+  constructor:->
+
+    super
+
+    @account = KD.whoami()
+
+    @avatar = new AvatarStaticView @getAvatarOptions(), @account
+
+    @emailForm = new KDFormViewWithFields
+      fields               :
+        firstName          :
+          placeholder      : "firstname"
+          name             : "firstName"
+          cssClass         : "thin half"
+          nextElement      :
+            lastName       :
+              cssClass     : "thin half"
+              placeholder  : "lastname"
+              name         : "lastName"
+        email              :
+          cssClass         : "thin"
+          placeholder      : "you@yourdomain.com"
+          name             : "email"
+          testPath         : "account-email-input"
+        username           :
+          cssClass         : "thin"
+          placeholder      : "username"
+          name             : "username"
+          attributes       : readonly : "true"
+          testPath         : "account-username-input"
+        password           :
+          cssClass         : "thin half"
+          placeholder      : "password"
+          name             : "password"
+          type             : "password"
+          nextElement      :
+            confirm        :
+              cssClass     : "thin half"
+              placeholder  : "confirm password"
+              name         : "confirmPassword"
+              type         : "password"
+      buttons              :
+        Save               :
+          title            : 'SAVE CHANGES'
+          type             : 'submit'
+          style            : 'solid green fr'
+      callback             : @bound 'update'
+
+  update:(formData)->
+
+    {email} = formData
+
+    # fixme: if new pass inputs filled do change password
+    # CHANGE PASSWORD
+    # KD.remote.api.JUser.changePassword formData.password,(err,docs)=>
+    #   unless err then do @passwordDidUpdate
+
+    # CHANGE EMAIL
+
+    KD.remote.api.JUser.changeEmail {email}, (err, result)=>
+
+      return notify err.message  if err and err.name isnt 'PINExistsError'
+
+      notify err.message  if err and err.name is 'PINExistsError'
+
+      new VerifyPINModal 'Update E-Mail', (pin)=>
+        KD.remote.api.JUser.changeEmail {email, pin}, (err)=>
+          notify if err then err.message else "E-mail changed!"
+          @emit "EmailChangedSuccessfully", email
+

   viewAppended:->
+
     KD.remote.api.JUser.fetchUser (err,user)=>
-      @putContents KD.whoami(), user

-  putContents:(account, user)->
+      @user    = user

-    # #
-    # ADDING EMAIL FORM
-    # #
-    @addSubView @emailForm = emailForm = new KDFormView
-      callback     : (formData)->
-        KD.remote.api.JUser.changeEmail
-          email : formData.email
-        , (err, result)=>
-          log err
-          if err and err.name isnt 'PINExistsError'
-            new KDNotificationView
-              title    : err.message
-              duration : 2000
-          else
-            if err and err.name is 'PINExistsError'
-              new KDNotificationView
-                title    : err.message
-                duration : 2000
-            new VerifyPINModal 'Update E-Mail', (pin)=>
-              KD.remote.api.JUser.changeEmail
-                email : formData.email
-                pin   : pin
-              , (err)=>
-                new KDNotificationView
-                  title    : if err then err.message else "E-mail changed!"
-                  duration : 2000
-                @emit "EmailChangedSuccessfully", formData.email
+      super

-          emailSwappable.swapViews()
+      @putDefaults()

-    emailForm.addSubView emailLabel = new KDLabelView
-      title        : "Your email"
-      cssClass     : "main-label"

-    emailInputs = new KDView cssClass : "hiddenval clearfix"
-    emailInputs.addSubView emailInput = new KDInputView
-      label        : emailLabel
-      defaultValue : user.email
-      placeholder  : "you@yourdomain.com..."
-      name         : "email"
-      testPath     : "account-email-input"
+  putDefaults:->

-    emailInputs.addSubView inputActions = new KDView cssClass : "actions-wrapper"
-    inputActions.addSubView emailSave = new KDButtonView
-      title        : "Save"
-      type         : 'submit'
-    inputActions.addSubView emailCancel = new KDCustomHTMLView
-      tagName      : "a"
-      partial      : "cancel"
-      cssClass     : "cancel-link"
-      click        : => emailSwappable.swapViews()
+    {email} = @user
+    {nickname, firstName, lastName} = @account.profile

-    # EMAIL STATIC PART
-    nonEmailInputs = new KDView cssClass : "initialval clearfix"
+    @emailForm.inputs.email.setDefaultValue email
+    @emailForm.inputs.username.setDefaultValue nickname
+    @emailForm.inputs.firstName.setDefaultValue firstName
+    @emailForm.inputs.lastName.setDefaultValue lastName

-    nonEmailInputs.addSubView emailSpan = new KDCustomHTMLView
-      tagName      : "span"
-      partial      : user.email
-      cssClass     : "static-text status-#{user.status}"
-
-    emailForm.on "EmailChangedSuccessfully", (email)->
-      emailSpan.updatePartial email
-
-    nonEmailInputs.addSubView emailEdit = new KDCustomHTMLView
-      tagName      : "a"
-      partial      : "Edit"
-      cssClass     : "action-link"
-      testPath     : "account-email-edit"
-      click        : => emailSwappable.swapViews()
-
-    verifyEmail = new KDView
-    if user.status is "unconfirmed"
-      verifyEmail = new KDCustomHTMLView
+    if @user.status is "unconfirmed"
+      o =
         tagName      : "a"
         partial      : "You didn't verify your email yet Verify now"
         cssClass     : "action-link verify-email"
         testPath     : "account-email-edit"
         click        : =>
-          KD.remote.api.JEmailConfirmation.resetToken KD.whoami().profile.nickname, (err)=>
+          KD.remote.api.JEmailConfirmation.resetToken @account.profile.nickname, (err)=>

             message = "Email confirmation mail is sent"
-            if err
-              message = err.message
-            else
-              verifyEmail.hide()
+            if err then message = err.message else @verifyEmail.hide()

             new KDNotificationView
               title    : message
               duration : 3500

-    nonEmailInputs.addSubView verifyEmail
+    @addSubView @verifyEmail = new KDCustomHTMLView o

-    # SET EMAIL SWAPPABLE
-    emailForm.addSubView emailSwappable = new AccountsSwappable
-      views    : [emailInputs,nonEmailInputs]
-      cssClass : "clearfix"

-    # #
-    # ADDING USERNAME FORM
-    # #
-    @addSubView usernameForm = usernameForm = new KDFormView
-      callback     : (formData)->
-        # KD.whoami().changeUsername formData.username
-        new KDNotificationView
-          type  : "mini"
-          title : "Currently disabled!"
+  getAvatarOptions:->

-    usernameForm.addSubView usernameLabel = new KDLabelView
-      title        : "Your username"
-      cssClass     : "main-label"
+    showStatus    : yes
+    tooltip       :
+      title       : "
Click avatar to edit
"
+      placement   : "below"
+      arrow       : placement : "top"
+    size          :
+      width       : 160
+      height      : 160
+    click         : =>
+      KD.singleton('appManager').create 'Activity', =>
+        pos =
+          top  : @avatar.getY() - 8
+          left : @avatar.getX() - 8

-    usernameInputs = new KDView cssClass : "hiddenval clearfix"
-    usernameInputs.addSubView usernameInput = new KDInputView
-      label        : usernameLabel
-      defaultValue : account.profile.nickname
-      placeholder  : "username..."
-      name         : "username"
-      testPath     : "account-username-input"
-    usernameInputs.addSubView inputActions = new KDView cssClass : "actions-wrapper"
-    inputActions.addSubView usernameSave = new KDButtonView
-      title        : "Save"
-      type         : "submit"
-    inputActions.addSubView usernameCancel = new KDCustomHTMLView
-      tagName      : "a"
-      partial      : "cancel"
-      cssClass     : "cancel-link"
-      click        : => usernameSwappable.swapViews()
+        @avatarMenu?.destroy()
+        @avatarMenu = new JContextMenu
+          menuWidth  : 312
+          cssClass   : "avatar-menu dark"
+          delegate   : @avatar
+          x          : @avatar.getX() + 96
+          y          : @avatar.getY() - 7
+        , customView : @avatarChange = new AvatarChangeView delegate: this, @account

-    # USERNAME STATIC PART
-    usernameNonInputs = usernameNonInputs = new KDView cssClass : "initialval clearfix"
-    usernameNonInputs.addSubView usernameSpan = new KDCustomHTMLView
-      tagName      : "span"
-      partial      : account.profile.nickname
-      cssClass     : "static-text"
-    usernameNonInputs.addSubView usernameEdit = new KDCustomHTMLView
-      tagName      : "a"
-      partial      : "Edit"
-      cssClass     : "action-link"
-      testPath     : "account-username-edit"
-      click        : => usernameSwappable.swapViews()
+        @avatarChange.on "UseGravatar", => @account.modify "profile.avatar" : ""

-    # SET USERNAME SWAPPABLE
-    usernameForm.addSubView usernameSwappable = new AccountsSwappable
-      views    : [usernameInputs,usernameNonInputs]
-      cssClass : "clearfix"
+        @avatarChange.on "UsePhoto", (dataURI)=>
+          [_, avatarBase64] = dataURI.split ","
+          @avatar.setAvatar "url(#{dataURI})"
+          @avatar.$().css
+            backgroundSize: "auto 90px"
+          @avatarChange.emit "LoadingStart"
+          @uploadAvatar avatarBase64, =>
+            @avatarChange.emit "LoadingEnd"
+
+  pistachio:->
+
+    """
+    {{> @avatar}}
+

+      {{> @emailForm}}
+

+    """
+


fix content locale key

fix content locale key

--- client/app/components/compose.coffee
+++ client/app/components/compose.coffee
@@ -136,7 +136,7 @@
                     label
                         htmlFor: 'compose-subject',
                         className: classLabel,
-                        t "content"
+                        t "compose content"
                     if @state.composeInHTML
                         div
                             className: 'rt-editor form-control',


Client: Proxifier: use koding.com proxy services for on-premise installs

--- client/app/lib/util/proxifier.coffee
+++ client/app/lib/util/proxifier.coffee
@@ -35,7 +35,11 @@
     subdomain = if isInProduction then 'p' else 'dev-p2'

     # create the base url
-    baseURL = "#{protocol}//#{subdomain}.#{globals.config.domains.base}/-"
+    # Since proxy is a service from koding.com we need to use it
+    # for on-premise version as well. otherwise all the on-premises
+    # users needs to run proxy server on their own ~GG
+    baseURL = "#{protocol}//#{subdomain}.koding.com/-"
+    # baseURL = "#{protocol}//#{subdomain}.#{globals.config.domains.base}/-"

     # if it's a tunnel given domain we need to do one more check
     # for tunnels since production tunnel proxy is different


ComputeController.ui: showComputeError: add description about error based on the content

--- client/app/lib/providers/computecontroller.ui.coffee
+++ client/app/lib/providers/computecontroller.ui.coffee
@@ -407,6 +407,15 @@
     { stack, errorMessage, title, subtitle, cssClass, message } = options

     cssClass ?= ''
+
+    if /^Bootstrap data is incomplete/.test errorMessage
+      errorMessage = """
+        Failed to complete request due to error with the provided credential.
+        Please contact with your team admin.
+
+        #{errorMessage}
+      """
+
     message   = if message then "
#{message}
" else ''

     modal     = new kd.ModalView


Zoom level affects mandatory border

Zoom level affects mandatory border

--- views/components/settings/parts/display-config.cjsx
+++ views/components/settings/parts/display-config.cjsx
@@ -149,16 +149,16 @@
       width = Math.min(width, window.innerWidth - 150)

     # Avoid setting a huge size by mistake
-    max_height = screen.height
-    max_width = screen.width
+    max_height = window.screen.availHeight
+    max_width = window.screen.availWidth
+    zoomLevel = config.get 'poi.zoomLevel', 1
     if config.get('poi.layout', 'horizontal') == 'horizontal'
-      max_width = max_width - (if window.doubleTabbed then 600 else 400)
+      max_width = max_width - (if window.doubleTabbed then 450 else 300) * zoomLevel
     else
-      max_height = max_height - 200
+      max_height = max_height - (200 * zoomLevel)
+      max_width = max_height / 480 * 800
+    width = Math.min(max_width, width)
     console.log max_width, max_height, width
-    max_width = max_height / 480 * 800
-    if width > max_width
-      width = max_width

     window.webviewWidth = width
     window.dispatchEvent new Event('webview.width.change')


activity: Fixed saving code snippets from activity.

--- client/landing/app/Applications/Activity.kdapplication/ListItems/ActivityListItemCodeSnippet.coffee
+++ client/landing/app/Applications/Activity.kdapplication/ListItems/ActivityListItemCodeSnippet.coffee
@@ -117,7 +117,7 @@
         title   : 'Save'
       callback  : ->
         {nickname} = KD.whoami().profile
-        rootPath   = "/Users/#{nickname}/Documents/CodeSnippets"
+        rootPath   = "/home/#{nickname}/Documents/CodeSnippets"
         fileName   = "#{@utils.slugify title}.#{__aceSettings.syntaxAssociations[syntax][1].split('|')[0]}"
         fullPath   = "#{rootPath}/#{fileName}"


Fixed bug when no rect defined for view

Fixed bug when no rect defined for view

--- js/foundation/thememanager/thememanager.coffee
+++ js/foundation/thememanager/thememanager.coffee
@@ -92,6 +92,7 @@
     return '' unless @themeHTMLTemplates[_themeName][_componentName]?
     [_tmplJS, _tmplHTML] = @themeHTMLTemplates[_themeName][_componentName]
     _rect   = _view.rect
+    _rect = [ 0, 0, 0, 0 ] unless _rect?
     _callArgs = [_view.elemId.toString(), _rect.width, _rect.height]
     _tmplHTML = _tmplHTML.replace(/\]I\[/g,_callArgs[0]).replace(/\]W\[/g,_callArgs[1]).replace(/\]H\[/g,_callArgs[2])
     return _tmplHTML if _tmplJS.length == 0


 Merge branch 'master.allowSuperAdminsToDelete' into 'master'

Groups: Super-admins can delete all groups.

--- workers/social/lib/social/models/group/index.coffee
+++ workers/social/lib/social/models/group/index.coffee
@@ -1224,7 +1224,8 @@
     @fetchOwner (err, owner)=>
       return callback err if err
       unless owner.getId().equals client.connection.delegate.getId()
-        return callback new KodingError 'You must be the owner to perform this action!'
+        unless client.connection.delegate.can "reset groups"
+          return callback new KodingError 'You must be the owner to perform this action!'

       removeHelper = (model, err, callback, queue)->
         return callback err if err


removing console.log

removing console.log

--- src/scripts/collections/role-acceptances.coffee
+++ src/scripts/collections/role-acceptances.coffee
@@ -20,4 +20,3 @@
         url: "#{AUTHORING}/contents#{id}@draft/acceptance"
       .done () =>
         @fetch({reset: true})
-        console.log JSON.stringify(data)


UserEnvironmentDataProvider: Use getter instead of globals.userEnvironmentData.

--- client/app/lib/userenvironmentdataprovider.coffee
+++ client/app/lib/userenvironmentdataprovider.coffee
@@ -35,33 +35,31 @@

     return no  unless @hasData()

-    for key, section of globals.userEnvironmentData
+    for key, section of @get()
       for obj in section
         obj.machine = remote.revive obj.machine
         for ws, i in obj.workspaces
           obj.workspaces[i] = remote.revive ws

-  getMyMachines: -> return globals.userEnvironmentData.own
+  getMyMachines: -> return @get().own

   getSharedMachines: ->

-    { shared } = globals.userEnvironmentData
+    { shared } = @get()

     return shared.concat @getCollaborationMachines()

   getCollaborationMachines: ->

-    data = globals.userEnvironmentData.collaboration
-
-    return data
+    return @get().collaboration

   getAllMachines: ->

-    { own, shared, collaboration } = globals.userEnvironmentData
+    { own, shared, collaboration } = @get()

     return own.concat shared.concat collaboration


chage port

chage port

--- server/init/index.coffee
+++ server/init/index.coffee
@@ -1,7 +1,8 @@
-debug = require('debug')('myapp')
+# debug = require('debug')('myapp')
 app = require '../app'

-app.set 'port', process.env.PORT or 3000
+app.set 'port', process.env.PORT or 3002

 server = app.listen app.get('port'), ->
-  debug "Express server listening on port #{server.address().port}"
+  # debug "Express server listening on port #{server.address().port}"
+  console.log "Express server listening on port #{server.address().port}"


Limit sample data only to 100 publications.

--- server/jobs/arxiv.coffee
+++ server/jobs/arxiv.coffee
@@ -49,7 +49,7 @@
     thisJob = @getQueueJob()
     count = 0

-    for recordEntry in page['OAI-PMH'].ListRecords[0].record
+    for recordEntry in page['OAI-PMH'].ListRecords[0].record[0..100]
       record = recordEntry.metadata?[0].arXivRaw?[0]

       if not record?


status displaying

status displaying

--- app/scripts/editor.coffee
+++ app/scripts/editor.coffee
@@ -12,31 +12,46 @@
 		$('#debug-keydown').text e.keyCode

 		switch e.keyCode
-			when 16 then @status.shift = true
-			when 17 then @status.ctrl = true
-			when 18 then @status.alt = true
-			when 91 then @status.cmd = true
+			when 16
+				@status.shift = true
+				$('.shift').removeClass('hidden')
+			when 17
+				@status.ctrl = true
+				$('.ctrl').removeClass('hidden')
+			when 18
+				@status.alt = true
+				$('.alt').removeClass('hidden')
+			when 91
+				@status.cmd = true
+				$('.cmd').removeClass('hidden')

 	handleKeyUp: (e)->
 		# debug
 		$('#debug-keyup').text e.keyCode

 		switch e.keyCode
-			when 16 then @status.shift = false
-			when 17 then @status.ctrl = false
-			when 18 then @status.alt = false
-			when 91 then @status.cmd = false
+			when 16
+				@status.shift = false
+				$('.shift').addClass('hidden')
+			when 17
+				@status.ctrl = false
+				$('.ctrl').addClass('hidden')
+			when 18
+				@status.alt = false
+				$('.alt').addClass('hidden')
+			when 91
+				@status.cmd = false
+				$('.cmd').addClass('hidden')

 	init: ->
 		@update()

 		# binding
-		$('#editor').on 'keyup', (e)=>
+		$('#editor').on 'keydown', (e)=>
+			@handleKeyDown(e)
+		.on 'keyup', (e)=>
+			@handleKeyUp(e)
 			@update()
-			@handleKeyUp(e)
-		.on 'keydown', (e)=>
-			console.log e
-			@handleKeyDown(e)

 $(document).ready ->
 	editor = new Editor


bubbles: max bubble size decreased

bubbles: max bubble size decreased

--- app/charts/bubbles.coffee
+++ app/charts/bubbles.coffee
@@ -340,7 +340,7 @@
           d3.max(maxTotalMagnitudes.outbound))

     rscale = d3.scale.sqrt()
-      .range([0, Math.min(bubblesChartWidth/20, bubblesChartHeight /10)])
+      .range([0, Math.min(bubblesChartWidth/25, bubblesChartHeight /12)])
       .domain([0, maxTotalMagnitude])
     maxr = rscale.range()[1]


Fix React bind warning

Fix React bind warning

--- app/classifier/index.cjsx
+++ app/classifier/index.cjsx
@@ -238,7 +238,7 @@
                   Demo mode:

                   No classifications are being recorded.{' '}
-                  <button type="button" className="secret-button" onClick={@props.onChangeDemoMode.bind(this, false)}>
+                  <button type="button" className="secret-button" onClick={@changeDemoMode.bind(this, false)}>
                     Disable
                   </button>

@@ -408,6 +408,9 @@

   toggleExpertClassification: (value) ->
     @setState showingExpertClassification: value
+
+  changeDemoMode: (demoMode) ->
+    @props.onChangeDemoMode demoMode

   subjectIsGravitySpyGoldStandard: ->
     @props.workflow.configuration?.gravity_spy_gold_standard and @props.subject.metadata?['#Type'] is 'Gold'


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/oscillator.coffee
+++ src/oscillator.coffee
@@ -3,6 +3,7 @@

 @name Oscillator
 @constructor
+
 ###
 Oscillator = (options={}) ->
   {amplitude, period, offset} = options


Fixed: Two network property dialogs opening

--- src/main/webapp/lib/views/MapNetworkView.coffee
+++ src/main/webapp/lib/views/MapNetworkView.coffee
@@ -35,7 +35,6 @@
     @_layersMenu()
     @_modeMenu()
     # handle network browser event
-    @_setUpEvents()
     $a.Util.publishEvents($a.broker, @broker_events, @)

     # This class creates the tree view of all the elements of the scenario
@@ -59,10 +58,6 @@

   routeMode: ->
     $a.broker.trigger('app:display_message:info', @ROUTE_EDIT_MSG)
-
-  _setUpEvents: ->
-    $a.broker.on('map:open_network_editor', @_editor, @)
-    $a.broker.on("map:clear_map", @_tearDownEvents, @)

   _tearDownEvents: ->
     # Commenting out since this ends up preventing the network properties editor from being lauched


Fixed callbacks

Fixed callbacks

--- js-library/app/js/jail_iframe/classes/icon_buttons.coffee
+++ js-library/app/js/jail_iframe/classes/icon_buttons.coffee
@@ -22,9 +22,9 @@

     @_robustHover = new FactlinkJailRoot.RobustHover
       $el: @$el
-      mouseenter: options.mouseenter
-      mouseleave: options.mouseleave
-    @$el.on 'click', options.mouseclick
+      mouseenter: options.onmouseenter
+      mouseleave: options.onmouseleave
+    @$el.on 'click', options.onclick

     targetColor = $targetElement.css('color')

@@ -68,9 +68,9 @@
     @_iconButton = new IconButton
       targetElement: textContainer
       targetOffset: "#{verticalOffsetPercentage}% 0"
-      mouseenter: @_onHover
-      mouseleave: @_onUnhover
-      click: @_onClick
+      onmouseenter: @_onHover
+      onmouseleave: @_onUnhover
+      onclick: @_onClick

     @_factId = factId

@@ -102,9 +102,9 @@
     @_iconButton = new IconButton
       content: '+'
       targetElement: @$paragraph[0]
-      mouseenter: => @_attentionSpan.gainAttention()
-      mouseleave: => @_attentionSpan.loseAttention()
-      click: @_onClick
+      onmouseenter: => @_attentionSpan.gainAttention()
+      onmouseleave: => @_attentionSpan.loseAttention()
+      onclick: @_onClick

     @_attentionSpan = new FactlinkJailRoot.AttentionSpan
       wait_for_neglection: 500


Fix Parser.update to use $.append properly

--- vendor/assets/javascripts/chosen/parser.js.coffee
+++ vendor/assets/javascripts/chosen/parser.js.coffee
@@ -77,7 +77,7 @@

     for attrs in data
       unless $.grep(selected_options, (o, i) => o.value is attrs.value.toString() and o.label == attrs.label.toString() ).length
-        @chosen.$target.append("<option />", value: attrs.value, html: attrs.label, data: { source: attrs })
+        @chosen.$target.append($("<option />", value: attrs.value, html: attrs.label, data: { source: attrs }))

     @parse(selected_options)
     return @


Making things work in Firefox, Chromium

Making things work in Firefox, Chromium

Note that you will need to run this from a local webserver.

--- src/js/marbleous.coffee
+++ src/js/marbleous.coffee
@@ -13,7 +13,7 @@
 CANVAS_HEIGHT      = 8 * BLOCK_SIZE
 CANVAS_WIDTH       = 7 * BLOCK_SIZE

-TEXTURE_FILE       = 'img/textures.png'
+TEXTURE_FILE       = '/img/textures.png'
 TEXTURE_BLOCK_SIZE = 101

 # All the types of blocks we support
@@ -209,8 +209,8 @@
           [screenX, screenY] = @renderingCoordinatesForBlock blockX, blockY, blockZ

           # TODO: There seems to be a minor difference on Safari – investigate
-          if screenX <= x <= (screenX + BLOCK_SIZE) and screenY <= y <= (screenY + BLOCK_SIZE)
-            pixel = @getTexture('hitboxes','block').getContext('2d').getImageData x - screenX, y - screenY, 0, 0
+          if screenX <= x < (screenX + BLOCK_SIZE) and screenY <= y < (screenY + BLOCK_SIZE)
+            pixel = @getTexture('hitboxes','block').getContext('2d').getImageData x - screenX, y - screenY, 1, 1

             return currentBlock if pixel.data[3] > 0

@@ -265,16 +265,20 @@
     console.timeEnd "rendering" if DEBUG

     $('#main-canvas').bind 'mousemove', (event) =>
-      blockAtMouse = @renderer.blockAtScreenCoordinates event.offsetX, event.offsetY
+      blockAtMouse = @renderer.blockAtScreenCoordinates event.layerX, event.layerY
       if blockAtMouse
-        $('body').css "cursor", "-webkit-grab"
+        if $.browser.webkit
+          cursor = "-webkit-grab"
+        else if $.browser.mozilla
+          cursor = "-moz-grab"
+        $('body').css "cursor", cursor
       else
         $('body').css "cursor", "auto"

     $('#main-canvas').bind 'click', (event) =>
       console.time "hit-test" if DEBUG
-      blockAtMouse = @renderer.blockAtScreenCoordinates event.offsetX, event.offsetY
+      blockAtMouse = @renderer.blockAtScreenCoordinates event.layerX, event.layerY
       console.log "Clicked block of type #{blockAtMouse.type}" if blockAtMouse
       console.timeEnd "hit-test" if DEBUG

-      return this+      return this


Save data when the user load a old query

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -2214,7 +2214,10 @@

         load_query: (event) =>
             id = @$(event.target).data().id
+            # Set + save codemirror
             @container.codemirror.setValue @history[parseInt(id)]
+            @container.saved_data.current_query = @history[parseInt(id)]
+            @container.save_data_in_localstorage()

         add_query: (query) =>
             # We don't keep state because we consider that people will not fire two differents queries in less than 200ms.


Remove a console.log

Remove a console.log

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -3266,7 +3266,6 @@
                     str_pieces = d_shifted_bis.toString().match(/([^ ]* )([^ ]* )([^ ]* )([^ ]* )(\d{2})(.*)/)
                     hours = parseInt(str_pieces[5])
                     hours++
-                    console.log hours
                     if hours.toString().length is 1
                         hours = "0"+hours.toString()
                     else


* Properties.initializeTo should return nothing

--- src/properties/index.coffee
+++ src/properties/index.coffee
@@ -92,6 +92,7 @@
         )(vAttrName, vAttr.assign)
       defineProperty dest, k, value, vAttr
       dest[k] = value if dest[k] != value # assign the initialization value after define property.
+    return
   getRealAttrName: (name)->
     name = @ixNames[name] unless @hasOwnProperty name
     name


Add defaults for autodave and excludeVcsIgnoredPaths

--- src/app/root-view.coffee
+++ src/app/root-view.coffee
@@ -21,7 +21,9 @@
   @version: 1

   @configDefaults:
+    autosave: false
     ignoredNames: [".git", ".svn", ".DS_Store"]
+    excludeVcsIgnoredPaths: false
     disabledPackages: []
     themes: ['atom-dark-ui', 'atom-dark-syntax']


Fix spelling mistake

Fix spelling mistake

--- lib/assets/javascripts/unpoly/classes/request.coffee
+++ lib/assets/javascripts/unpoly/classes/request.coffee
@@ -164,7 +164,7 @@
   ###**
   The element that triggered the request.

-  For example, when this request was triggered by a click on a link, the lonk
+  For example, when this request was triggered by a click on a link, the link
   element is set as the `{ origin }`.

   To prevent memory leaks, this property is removed shortly after the response is received.


no caching on channels

no caching on channels

--- apps/channel/routes.coffee
+++ apps/channel/routes.coffee
@@ -20,7 +20,6 @@
     res.locals.sd.CLIENT_PATH = "block/#{req.params.block_id}"

   channel.fetch
-    cache: true
     success: ->
       blocks.add channel.get 'contents'


Workspace: Set delegate as origin strictly for exposed method

--- workers/social/lib/social/models/workspace.coffee
+++ workers/social/lib/social/models/workspace.coffee
@@ -43,13 +43,21 @@
       instance     : []

-  @create = secure (client, data, callback) ->
+  @create$ = secure (client, data, callback) ->

     {delegate}    = client.connection
     data.originId = delegate._id

-    nickname      = delegate.profile.nickname
-    data.slug     = slugify data.name?.toLowerCase()
+    @create client, data, callback
+
+
+  @create = secure (client, data, callback) ->
+
+    {delegate}     = client.connection
+    data.originId ?= delegate._id
+
+    nickname  = delegate.profile.nickname
+    data.slug = slugify data.name?.toLowerCase()

     {name, slug, machineUId, rootPath, originId, machineLabel} = data


Fix pool reference in restart

Fix pool reference in restart

(its still messed up though)

--- src/pow/server.coffee
+++ src/pow/server.coffee
@@ -43,7 +43,7 @@
     sys.pump app.pool.stderr, process.stdout

     fs.watchFile "#{root}/tmp/restart.txt", (curr, prev) ->
-      pool.quit()
+      app.pool.quit()

     app


Use mutable approach for findElements

Use mutable approach for findElements

--- src/primitives.coffee
+++ src/primitives.coffee
@@ -85,13 +85,11 @@

   add: (val) -> @push val

-  findElements: (cond) ->
-    elements = @content.reduce (results, el) ->
-      return results.concat([el.findElements(cond)]) if el.elementType() in ['array', 'object']
-      return results.concat([el]) if cond(el)
-      return results
-    , []
-    _.flatten elements, true
+  findElements: (cond, results = []) ->
+    @content.forEach (el) ->
+      el.findElements(cond, results) if el.elementType() in ['array', 'object']
+      results.push(el) if cond(el)
+    results

   find: (cond) ->
     newArray = new ArrayType


share render for scores list

share render for scores list

--- resources/assets/coffee/react/profile-page/top-ranks.coffee
+++ resources/assets/coffee/react/profile-page/top-ranks.coffee
@@ -31,24 +31,7 @@

       div null,
         h3 className: 'page-extra__title page-extra__title--small', osu.trans('users.show.extra.top_ranks.best.title')
-        if @props.scoresBest?.length
-          div className: 'profile-extra-entries',
-            el PlayDetailList, scores: @props.scoresBest
-
-            div className: 'profile-extra-entries__item',
-              el ShowMoreLink,
-                modifiers: ['profile-page', 't-greyseafoam-dark']
-                event: 'profile:showMore'
-                hasMore: @props.pagination.scoresBest.hasMore
-                loading: @props.pagination.scoresBest.loading
-                data:
-                  name: 'scoresBest'
-                  url: laroute.route 'users.scores',
-                    user: @props.user.id
-                    type: 'best'
-                    mode: @props.currentMode
-        else
-          p className: 'profile-extra-entries', osu.trans('users.show.extra.top_ranks.empty')
+        @renderScores 'scoresBest', 'best'

       div null,
         h3
@@ -58,21 +41,29 @@
           if @props.user.scores_first_count[0] > 0
             span className: 'page-extra__title-count',
               osu.formatNumber(@props.user.scores_first_count[0])
-        if @props.scoresFirsts?.length
-          div className: 'profile-extra-entries',
-            el PlayDetailList, scores: @props.scoresFirsts

-            div className: 'profile-extra-entries__item',
-              el ShowMoreLink,
-                modifiers: ['profile-page', 't-greyseafoam-dark']
-                event: 'profile:showMore'
-                hasMore: @props.pagination.scoresFirsts.hasMore
-                loading: @props.pagination.scoresFirsts.loading
-                data:
-                  name: 'scoresFirsts'
-                  url: laroute.route 'users.scores',
-                    user: @props.user.id
-                    type: 'firsts'
-                    mode: @props.currentMode
-        else
-          p className: 'profile-extra-entries', osu.trans('users.show.extra.top_ranks.empty')
+        @renderScores 'scoresFirsts', 'firsts'
+
+
+  renderScores: (key, type) =>
+    pagination = @props.pagination[key]
+    scores = @props[key]
+
+    if scores?.length
+      div className: 'profile-extra-entries',
+        el PlayDetailList, scores: scores
+
+        div className: 'profile-extra-entries__item',
+          el ShowMoreLink,
+            modifiers: ['profile-page', 't-greyseafoam-dark']
+            event: 'profile:showMore'
+            hasMore: pagination.hasMore
+            loading: pagination.loading
+            data:
+              name: key
+              url: laroute.route 'users.scores',
+                user: @props.user.id
+                type: type
+                mode: @props.currentMode
+    else
+      p className: 'profile-extra-entries', osu.trans('users.show.extra.top_ranks.empty')


profile: checkboxes!

profile: checkboxes!

--- components/inputs/KDInputView.coffee
+++ components/inputs/KDInputView.coffee
@@ -7,7 +7,7 @@
     o.label                   or= null          # a KDLabelView instance
     o.cssClass                or= ""            # a String
     o.callback                or= null          # a Function
-    o.defaultValue            or= ""            # a String or a Boolean value depending on type
+    o.defaultValue             ?= ""            # a String or a Boolean value depending on type
     o.placeholder             or= ""            # a String
     o.disabled                 ?= no            # a Boolean value
     # o.readonly               ?= no            # a Boolean value
@@ -113,7 +113,11 @@
     @$().val @getDefaultValue()

   setDefaultValue:(value) ->
-    @getDomElement().val value if value isnt ""
+    if @getOption("type") is "checkbox"
+      @getDomElement().attr checked : value
+    else
+      @getDomElement().val value if value isnt ""
+
     @inputDefaultValue = value

   getDefaultValue:()->
@@ -131,18 +135,23 @@
     @getDomElement().removeAttr "disabled"

   getValue:()->
-    value = @getDomElement().val()
-    {forceCase} = @getOptions()
-    if forceCase
-      value = if /uppercase/i.test forceCase
-        value.toUpperCase()
-      else
-        value.toLowerCase()
+    if @getOption("type") is "checkbox"
+      value = @$().is ':checked'
+    else
+      value = @getDomElement().val()
+      {forceCase} = @getOptions()
+      if forceCase
+        value = if /uppercase/i.test forceCase
+          value.toUpperCase()
+        else
+          value.toLowerCase()

     return value

   setValue:(value)->
-    @getDomElement().val(value) if value?
+    if @getOption("type") is "checkbox"
+      @getDomElement().attr checked : value
+    else @getDomElement().val(value) if value?

   _prevVal = null

@@ -352,7 +361,7 @@
     @getSingleton("windowController").revertKeyView @
     yes

-  mouseDown:=>
+  mouseDown:->

     @setFocus()
     #WHY NO?


Fix the most important up.compiler example in the docs (ouch)

--- lib/assets/javascripts/unpoly/syntax.js.coffee
+++ lib/assets/javascripts/unpoly/syntax.js.coffee
@@ -46,11 +46,11 @@
   Registers a function to be called whenever an element with
   the given selector is inserted into the DOM.

-      $('.action').compiler(function($element) {
+      up.compiler('.action', function($element) {
         // your code here
       });

-  Compiler functions will be called on matching elements when
+  The functions will be called on elements maching `.action` when
   the page loads, or whenever a matching fragment is [updated through Unpoly](/up.replace)
   later.


minor cleanup

minor cleanup

--- server/lib/server/staticpages.coffee
+++ server/lib/server/staticpages.coffee
@@ -1,7 +1,6 @@
-{projectRoot} = KONFIG
-fs = require 'fs'
-
-defaultIndex = "#{projectRoot}/website/default.html"
+{projectRoot}   = KONFIG
+fs              = require 'fs'
+defaultIndex    = "#{projectRoot}/website/default.html"
 defaultTemplate = fs.readFileSync defaultIndex, 'utf-8'

-module.exports = defaultTemplate+module.exports  = defaultTemplate


tell action_logs about canceled

tell action_logs about canceled

--- js/inner/action_log.coffee
+++ js/inner/action_log.coffee
@@ -31,6 +31,7 @@
     @success = @komp => @status() == "success"
     @running = @komp => @status() == "running"
     @failed = @komp => @status() == "failed" or @status() == "timedout" or @status() == "cancelled" || @status() == "infrastructure_fail"
+    @canceled = @komp => @status() == "canceled"
     @infrastructure_fail = @komp => @status() == "infrastructure_fail"

@@ -59,6 +60,7 @@
       failed: @komp => @failed() or @infrastructure_fail()
       running: @komp => @running()
       success: @komp => @success()
+      canceled: @komp => @canceled()

     @action_header_button_style = @komp =>
       if @has_content()


Update Indonesian translations for `league` and `tournament` block (#6245)

* Update Indonesian translations  and  block

* Update Indonesian translations  and  block

* Update Indonesian translations fix 1 line

* Update Indonesian translations fix 1 line (2)

Co-authored-by: Nick Winter <75ef9faee755c70589550b513ad881e5a603182c@codecombat.com>


Remove unused method.

Remove unused method.

--- app/models/drawing.coffee
+++ app/models/drawing.coffee
@@ -8,7 +8,4 @@
   @hasMany "links", Link
   @extend Spine.Model.Local

-  @findOrCreate: (id) ->
-    if @exists(id) then @find(id) else @create(id: id)
-
 module.exports = Drawing


Initial implementation. Needs testing.

Initial implementation. Needs testing.

--- app/models/patient.coffee
+++ app/models/patient.coffee
@@ -87,6 +87,24 @@
     else
       Math.round(Math.random() * (92 - 65) + 65)

+  inpatientAdmissions: Ember.computed 'encounters', ->
+    @get('encounters').filter (item, index, self) ->
+      is_inpatient = false
+      item.get('type').get('coding')?.forEach (c, i) ->
+        if c.get('system') == 'http://www.ama-assn.org/go/cpt'
+          is_inpatient = ['99221', '99222', '99223'].contains(c.get('code'))
+      is_inpatient
+
+  readmissions: Ember.computed 'inpatientAdmissions', ->
+    @get('inpatientAdmissions').sortBy('period.end').reduce (previousValue, item, index, enumerable) ->
+      if previousValue?
+        previousValue.count++ if ((item.get('period.start') - previousValue.previousAdmission.get('period.end'))/(1000*60*60*24)) <= 30
+        previousValue.previousAdmission = item
+        previousValue
+      else
+        count: 0, previousAdmission: item
+    , null
+
   computedGender: Ember.computed 'gender', ->
     value = @get('gender')?.toString()
     if value == 'M' then 'male'


Only select from decals since illustris is complete

--- app/models/subject.coffee
+++ app/models/subject.coffee
@@ -58,6 +58,7 @@
   @url: (params) -> @withParams "/projects/galaxy_zoo/groups/#{ params.surveyId }/subjects", limit: params.limit

   @randomSurveyId: ->
+    return @::surveys.decals.id # illustris is complete
     n = Math.random()
     if n <= (2/3)
       @::surveys.decals.id


just adding a missing debug variable

just adding a missing debug variable

--- app/assets/javascripts/people.js.coffee
+++ app/assets/javascripts/people.js.coffee
@@ -1,5 +1,6 @@

 $ = jQuery
+debug = false

 $(document).ready ->
   $('#random-ruler-name').click ->