Extended German translation (about, minor fixes)

--- app/locale/de.coffee
+++ app/locale/de.coffee
@@ -217,20 +217,21 @@
     hipchat_url: "HipChat room."
     level_some_options: "Einige Einstellungsmöglichkeiten?"
 #    level_tab_thangs: "Thangs" # Things? # <= no Thangs are the components of the level. Check the editor if you're not sure what it is.
+#                                 rather dont't translate it
     level_tab_scripts: "Skripte"
     level_tab_settings: "Einstellungen"
     level_tab_components: "Komponenten"
     level_tab_systems: "Systeme"
-#    level_tab_thangs_title: "Current Thangs"
+    level_tab_thangs_title: "Aktuelle Thangs"
     level_tab_thangs_conditions: "Startbedingungen"
-#    level_tab_thangs_add: "Add Thangs"
+    level_tab_thangs_add: "Thangs hinzufügen"
     level_settings_title: "Einstellungen"
     level_component_tab_title: "Aktuelle Komponenten"
     level_component_btn_new: "neue Komponente erstellen"
     level_systems_tab_title: "Aktuelle Systeme"
     level_systems_btn_new: "neues System erstellen"
     level_systems_btn_add: "System hinzufügen"
-#    level_components_title: "Back to All Thangs"
+    level_components_title: "Zurück zu allen Thangs"
     level_components_type: "Typ"
     level_component_edit_title: "Komponente bearbeiten"
     level_system_edit_title: "System bearbeiten"
@@ -259,23 +260,23 @@
     who_is_codecombat: "Wer ist CodeCombat?" # Wer steckt hinter CodeCombat # behind
     why_codecombat: "Warum CodeCombat?"
     who_description_prefix: "gründeten CodeCombat im Jahre 2013 zusammen. Wir entwickelten außerdem "
-    who_description_suffix: ", die meist benutzte (Position #1) Web and iOS Applikation 2008 zum Schreiben lernen der Chinesischen und Japanischen Schriftzeichen." # need improvements
+    who_description_suffix: ", die meist benutzte (#1) Web and iOS Applikation 2008 zum Lernen des Schreibens von chinesischen und japanischen Schriftzeichen." # need improvements
     who_description_ending: "Nun ist es an der Zeit, den Leuten das Programmieren beizubringen."
-#    why_paragraph_1: "When making Skritter, George didn't know how to program and was constantly frustrated by his inability to implement his ideas. Afterwards, he tried learning, but the lessons were too slow. His housemate, wanting to reskill and stop teaching, tried Codecademy, but \"got bored.\" Each week another friend started Codecademy, then dropped off. We realized it was the same problem we'd solved with Skritter: people learning a skill via slow, intensive lessons when what they need is fast, extensive practice. We know how to fix that."
-#    why_paragraph_2: "Need to learn to code? You don't need lessons. You need to write a lot of code and have a great time doing it."
-#    why_paragraph_3_prefix: "That's what programming is about. It's gotta be fun. Not fun like"
-#    why_paragraph_3_italic: "yay a badge"
-#    why_paragraph_3_center: "but fun like"
-#    why_paragraph_3_italic_caps: "NO MOM I HAVE TO FINISH THE LEVEL!"
-#    why_paragraph_3_suffix: "That's why CodeCombat is a multiplayer game, not a gamified lesson course. We won't stop until you can't stop--but this time, that's a good thing."
-#    why_paragraph_4: "If you're going to get addicted to some game, get addicted to this one and become one of the wizards of the tech age."
-#    why_ending: "And hey, it's free. "
-#    why_ending_url: "Start wizarding now!"
-#    george_description: "CEO, business guy, web designer, game designer, and champion of beginning programmers everywhere."
-#    scott_description: "Programmer extraordinaire, software architect, kitchen wizard, and master of finances. Scott is the reasonable one."
-#    nick_description: "Programming wizard, eccentric motivation mage, and upside-down experimenter. Nick can do anything and chooses to build CodeCombat."
-#    jeremy_description: "Customer support mage, usability tester, and community organizer; you've probably already spoken with Jeremy."
-#    michael_description: "Programmer, sys-admin, and undergrad technical wunderkind, Michael is the person keeping our servers online."
+    why_paragraph_1: "Als er Skritter machte, wusste George nicht wie man programmiert und war permanent darüber frustriert, dass er seine Ideen nicht umsetzen konnte. Danach versuchte er es zu lernen, aber das ging ihm zu langsam. Sein Mitbewohner versuchte Codecademy, als er sich umorientierte und aufhörte zu lehren, aber \"langweilte sich\". Jede Woche begann ein neuer Freund mit Codecademy und ließ es dann wieder bleiben. Wir erkannten, dass es das gleiche Problem war, welches wir mit Skritter gelöst hatten: Leute lernen eine Fähigkeit mittels langsamer, intersiver Lerneinheiten, wobei sie schnelle, umfassende Übung bräuchten. Wir kennen Abhilfe."
+    why_paragraph_2: "Programmieren lernen? Du brauchst keine Stunden. Du musst einen Haufen Code schreiben und dabei Spaß haben."
+    why_paragraph_3_prefix: "Darum geht's beim Programmieren. Es soll Spaß machen. Nicht so einen Spaß wie"
+    why_paragraph_3_italic: "jau, 'ne Plakette"
+    why_paragraph_3_center: "sondern Spaß wie"
+    why_paragraph_3_italic_caps: "NEIN MUTTI ICH MUSS NOCH DEN LEVEL BEENDEN !"
+    why_paragraph_3_suffix: "Deshalb ist CodeCombat ein Multiplayerspiel und kein spielähnlicher Kurs. Wir werden nicht aufhören bis du nicht mehr aufhören kannst -- nur diesmal ist das eine gute Sache."
+    why_paragraph_4: "Wenn dich Spiele süchtig machen, dass lass dich von diesem süchtig machen und werde ein Zauberer des Technologiezeitalters."
+    why_ending: "Und hey, es kostet nichts. "
+    why_ending_url: "Beginne jetzt zu zaubern!"
+    george_description: "CEO, Businesstyp, Web Designer, Game Designer und Champion der Programmieranfänger überall."
+    scott_description: "Außergewöhnlicher Programmierer, Softwarearchitekt, Küchenzauberer und Finanzmeister. Scott ist der Vernünftige."
+    nick_description: "Programmierzauberer, exzentrischer Motivationskünstler und  Auf-den-Kopf-stell-Experimentierer. Nick könnte alles mögliche tun und entschied CodeCombat zu bauen."
+    jeremy_description: "Kundendienstmagier, Usability Tester und Community-Organisator. Wahrscheinlich hast du schon mit Jeremy gesprochen."
+    michael_description: "Programmierer, Systemadministrator und studentisch technisches Wunderkind, Michael hält unsere Server am Laufen."

   legal:
     page_title: "Rechtliches"
@@ -298,7 +299,7 @@
     cost_title: "Kosten"
     cost_description: "CodeCombat ist zur Zeit 100% kostenlos! Eines unserer Hauptziele ist, es dabei zu belassen, so dass es so viele Leute wie möglich spielen können, unabhängig davon in welcher Lebenssituation sie sich befinden. Falls dunkle Wolken aufziehen, könnten wir manche Inhalte im Rahmen eines Abonnements anbieten, aber lieber nicht. Mit etwas Glück können wir die Firma erhalten durch:"
     recruitment_title: "Recruiting"
-    recruitment_description_prefix: "Hier bei CodeCombat kannst du ein mächtiger Zauberer werden, nicht nur Spiel, sondern auch in der Realität."
+    recruitment_description_prefix: "Hier bei CodeCombat kannst du ein mächtiger Zauberer werden, nicht nur im Spiel, sondern auch in der Realität."
     url_hire_programmers: "Niemand kann schnell genug Programmierer einstellen."
     recruitment_description_suffix: "So wenn du deine Fähigkeiten entwickelt hast und zustimmst, werden wir deine besten Leistungen den tausenden Arbeitgebern demonstrieren, welche nur auf die Gelegentheit warten, dich einzustellen. Sie bezahlen uns ein bisschen, und sie bezahlen dir "
     recruitment_description_italic: "jede Menge"


Update en.coffee

Update en.coffee

--- app/locale/en.coffee
+++ app/locale/en.coffee
@@ -926,7 +926,7 @@
     email_description_suffix: "or through links in the emails we send, you can change your preferences and easily unsubscribe at any time."
     cost_title: "Cost"
     cost_description: "Currently, CodeCombat is 100% free! One of our main goals is to keep it that way, so that as many people can play as possible, regardless of place in life. If the sky darkens, we might have to charge subscriptions or for some content, but we'd rather not. With any luck, we'll be able to sustain the company with:"
-    cost_description_2: "CodeCombat is free to play in the dungeon campaign, with a $9.99/mo subscription for access to later campaigns and 3500 bonus gems per month. You can cancel with a click, and we offer a 100% money-back guarantee."
+    cost_description_2: "CodeCombat is free to play in the dungeon campaign, with a $9.99 USD/mo subscription for access to later campaigns and 3500 bonus gems per month. You can cancel with a click, and we offer a 100% money-back guarantee."
     copyrights_title: "Copyrights and Licenses"
     contributor_title: "Contributor License Agreement"
     contributor_description_prefix: "All contributions, both on the site and on our GitHub repository, are subject to our"


Add missing # {change} to en.coffee

Add missing # {change} to en.coffee

Also testing web UI for GitHub.  Seems good.

--- app/locale/en.coffee
+++ app/locale/en.coffee
@@ -940,7 +940,7 @@
     cat_title: "Game Designer"
     scott_title: "Cofounder, Software Engineer"
     maka_title: "Customer Advocate"
-    robin_title: "Senior Product Manager"
+    robin_title: "Senior Product Manager" # {change}
     nolan_title: "Sales Manager"
     lisa_title: "Business Development Manager"
     sean_title: "Territory Manager"


Update id.coffe

Update id.coffe

--- app/locale/id.coffee
+++ app/locale/id.coffee
@@ -15,12 +15,12 @@

   nav:
     play: "Levels" # The top nav bar entry where players choose which levels to play
-    community: "Community"
+    community: "Komunitas"
     editor: "Editor"
     blog: "Blog"
     forum: "Forum"
     account: "Akun"
-    profile: "Profile"
+    profile: "Profil"
     stats: "Mulai"
     code: "Code"
     admin: "Admin" # Only shows up when you are an admin
@@ -33,7 +33,7 @@
 #    teachers: "Teachers"

   modal:
-    close: "Close"
+    close: "Tutup"
     okay: "Okay"

   not_found:
@@ -42,8 +42,8 @@
   diplomat_suggestion:
 #    title: "Help translate CodeCombat!" # This shows up when a player switches to a non-English language using the language selector.
 #    sub_heading: "We need your language skills."
-    pitch_body: "We develop CodeCombat in English, but we already have players all over the world. Many of them want to play in Indonesian but don't speak English, so if you can speak both, please consider signing up to be a Diplomat and help translate both the CodeCombat website and all the levels into Indonesian."
-    missing_translations: "Until we can translate everything into Indonesian, you'll see English when Indonesian isn't available."
+    pitch_body: "Kami mengembangkan CodeCombat dalam bahasa Inggris, tapi kami sudah memiliki pemain di seluruh dunia. Banyak dari mereka ingin bermain di Indonesia, tetapi tidak berbicara bahasa Inggris, jadi jika Anda dapat berbicara, silakan mempertimbangkan untuk mendaftar untuk menjadi Diplomat dan membantu menerjemahkan kedua situs CodeCombat dan semua tingkatan ke Indonesia."
+    missing_translations: "Hingga kami bisa menerjemahkan semuanya ke dalam bahasa Indonesia, Anda akan melihat bahasa Inggris ketika Indonesia belum tersedia."
 #    learn_more: "Learn more about being a Diplomat"
 #    subscribe_as_diplomat: "Subscribe as a Diplomat"

@@ -58,12 +58,12 @@
 #    owned: "Owned" # For items you own
     locked: "Terkunci"
 #    purchasable: "Purchasable" # For a hero you unlocked but haven't purchased
-    available: "Trsedia"
+    available: "Tersedia"
 #    skills_granted: "Skills Granted" # Property documentation details
     heroes: "Heroes" # Tooltip on hero shop button from /play
 #    achievements: "Achievements" # Tooltip on achievement list button from /play
     account: "Akun" # Tooltip on account button from /play
-    settings: "Settings" # Tooltip on settings button from /play
+    settings: "Pengaturan" # Tooltip on settings button from /play
 #    poll: "Poll" # Tooltip on poll button from /play
 #    next: "Next" # Go from choose hero to choose inventory before playing a level
 #    change_hero: "Change Hero" # Go back from choose inventory to choose hero


Update ja.coffee

Update ja.coffee

--- app/locale/ja.coffee
+++ app/locale/ja.coffee
@@ -150,10 +150,10 @@
     adjust_volume: "音量を調整する"
     campaign_multiplayer: "マルチプレイ・アリーナ"
     campaign_multiplayer_description: "コーディングで他のプレイヤーに対して格闘しましょう"
-#    brain_pop_done: "You’ve defeated the Ogres with code! You win!"
-#    brain_pop_challenge: "Challenge yourself to play again using a different programming language!"
-#    replay: "Replay"
-#    back_to_classroom: "Back to Classroom"
+    brain_pop_done: "勝利！コードでオーガを倒した！"
+    brain_pop_challenge: "違うプログラミング言語での再挑戦で自分自身にチャレンジ!"
+    replay: "リプレイ"
+    back_to_classroom: "クラスルームに戻る"

   code:
     if: "もし" # Keywords--these translations show up on hover, so please translate them all, even if it's kind of long. (In the code editor, they will still be in English.)
@@ -320,8 +320,8 @@
   common:
     back: "戻る" # When used as an action verb, like "Navigate backward"
     coming_soon: "近日公開!"
-    continue: "次へ" # When used as an action verb, like "Continue forward"
-#    next: "Next"
+    continue: "続ける" # When used as an action verb, like "Continue forward"
+    next: "次へ"
     default_code: "元のコード"
     loading: "ロード中"
     overview: "あらすじ"
@@ -347,7 +347,7 @@
     submit_changes: "変更を送信"
     save_changes: "変更を保存"
     required_field: "記入漏れ" # {change}
-#    valid_phone: "Enter a valid phone number."
+    valid_phone: "認証された電話番号で入る."

   general:
     and: "と"
@@ -627,13 +627,13 @@
     prompt_button: "ショップに入る"
     recovered: "前のジェム購入をリカバリーしました。ページを更新してください。"
     price: "x{{gems}} / 月"
-#    buy_premium: "Buy Premium"
-#    purchase: "Purchase"
-#    purchased: "Purchased"
-
-#  earn_gems:
-#    prompt_title: "Not Enough Gems"
-#    prompt_body: "Keep playing to earn more!"
+    buy_premium: "プラチナを購入"
+    purchase: "購入"
+    purchased: "購入完了"
+
+  earn_gems:
+    prompt_title: "ジェムが足りません"
+    prompt_body: "プレイを続けてさらに稼ごう！"

   subscribe:
     subscribe_modal_title: "CodeCombatプレミアム"
@@ -878,9 +878,9 @@
     elliot_title: "パートナーシップマネージャー"
     elliot_blurb: "読心術者"
     lisa_title: "市場開発代表" # {change}
-#    lisa_blurb: "A gritty one"
+    lisa_blurb: "荒々しい"
     sean_title: "地域部長"
-#    liz_title: "Territory Manager"
+    liz_title: "地域部長"
     retrostyle_title: "イラスト"
     retrostyle_blurb: "レトロスタイルのゲーム"
     jose_title: "ミュージック"
@@ -1099,7 +1099,7 @@
     saved: "変更しました"
     password_mismatch: "パスワードが違います"
     password_repeat: "もう一度パスワードを入力してください"
-#    file_size_limit: "(max file size: 1MB)"
+    file_size_limit: "(最大ファイルサイズ: 1MB)"

   keyboard_shortcuts:
     keyboard_shortcuts: "キーボードショートカット"


Translate Calendar Section

Translate Calendar Section

Translate Calendar Section. Day/Month/Year and Month Names

--- app/locale/th.coffee
+++ app/locale/th.coffee
@@ -2731,22 +2731,22 @@
 #    license: "license"
 #    oreilly: "ebook of your choice"

-#  calendar:
-#    year: "Year"
-#    day: "Day"
-#    month: "Month"
-#    january: "January"
-#    february: "February"
-#    march: "March"
-#    april: "April"
-#    may: "May"
-#    june: "June"
-#    july: "July"
-#    august: "August"
-#    september: "September"
-#    october: "October"
-#    november: "November"
-#    december: "December"
+  calendar:
+    year: "ปี"
+    day: "วัน"
+    month: "เดือน"
+    january: "มกราคม"
+    february: "กุมภาพันธ์"
+    march: "มีนาคม"
+    april: "เมษายน"
+    may: "พฤษภาคม"
+    june: "มิถุนายน"
+    july: "กรกฎาคม"
+    august: "สิงหาคม"
+    september: "กันยายน"
+    october: "ตุลาคม"
+    november: "พฤศจิกายน"
+    december: "ธันวาคม"

 #  code_play_create_account_modal:
 #    title: "You did it!" # This section is only needed in US, UK, Mexico, India, and Germany


Update tr.coffee

Update tr.coffee

--- app/locale/tr.coffee
+++ app/locale/tr.coffee
@@ -197,12 +197,12 @@
     victory_rate_the_level: "Seviyeyi oyla:" # Only in old-style levels.
     victory_return_to_ladder: "Merdivene Dön"
     victory_play_continue: "Devam Et"
-#    victory_play_skip: "Skip Ahead"
+    victory_play_skip: "İleri Atla"
     victory_play_next_level: "Sonraki Seviyeyi Oyna: "
-#    victory_play_more_practice: "More Practice"
-#    victory_play_too_easy: "Too Easy"
-#    victory_play_just_right: "Just Right"
-#    victory_play_too_hard: "Too Hard"
+    victory_play_more_practice: "Daha Fazla Alıştırma"
+    victory_play_too_easy: "Çok Kolay"
+    victory_play_just_right: "Tam Kıvamında"
+    victory_play_too_hard: "Çok Zor"
     victory_saving_progress: "İlerlemeyi Kaydediliyor"
     victory_go_home: "Anasayfaya Git" # Only in old-style levels.
     victory_review: "Daha fazla söyleyin!" # Only in old-style levels.
@@ -226,22 +226,22 @@
     tome_available_spells: "Kullanılabilir Büyüler"
     tome_your_skills: "Yetenekleriniz"
     hud_continue: "Devam (ÜstKarakter+Boşluk)"
-#    code_saved: "Code Saved"
-    skip_tutorial: "Atla (esc)"
+    code_saved: "Kod Kaydedildi"
+    skip_tutorial: "Atla (ESC)"
     keyboard_shortcuts: "Klavye Kısayolları"
     loading_ready: "Hazır!"
     loading_start: "Seviyeyi Başlat"
     time_current: "Şimdi:"
     time_total: "Max:"
     time_goto: "Git:"
-#    infinite_loop_try_again: "Try Again"
-#    infinite_loop_reset_level: "Reset Level"
-#    infinite_loop_comment_out: "Comment Out My Code"
-#    tip_toggle_play: "Toggle play/paused with Ctrl+P."
+    infinite_loop_try_again: "Yeniden Dene"
+    infinite_loop_reset_level: "Bölümü Sıfırla"
+    infinite_loop_comment_out: "Kodumu Yorum Yap"
+    tip_toggle_play: "Ctrl+P ile oynat/beklet."
 #    tip_scrub_shortcut: "Ctrl+[ and Ctrl+] rewind and fast-forward."
 #    tip_guide_exists: "Click the guide at the top of the page for useful info."
-#    tip_open_source: "CodeCombat is 100% open source!"
-#    tip_beta_launch: "CodeCombat launched its beta in October, 2013."
+    tip_open_source: "CodeCombat %100 açık kaynaktır!"
+    tip_beta_launch: "CodeCombat ilk beta sürümünü Ekim 2013'te başlattı."
 #    tip_think_solution: "Think of the solution, not the problem."
 #    tip_theory_practice: "In theory, there is no difference between theory and practice. But in practice, there is. - Yogi Berra"
 #    tip_error_free: "There are two ways to write error-free programs; only the third one works. - Alan Perlis"
@@ -275,14 +275,14 @@
     options_tab: "Seçenekler"
     guide_tab: "Rehber"
     multiplayer_tab: "Çoklu-oyuncu"
-#    auth_tab: "Sign Up"
+    auth_tab: "Kaydol"
     inventory_caption: "Kahramanınızı donatın"
     choose_hero_caption: "Kahraman, dil seçin"
     save_load_caption: "... ve geçmişe bak"
     options_caption: "Ayarları yapılandır"
     guide_caption: "Belgeler ve ipuçları"
     multiplayer_caption: "Arkadaşlarla oyna!"
-#    auth_caption: "Save your progress."
+    auth_caption: "İlerlemenizi kaydedin."

   inventory:
     choose_inventory: "Ögeleri Donan"
@@ -299,29 +299,29 @@
     io_blurb: "Basit fakat anlaşılması güç."
     status: "Durum"
     weapons: "Silahlar"
-#    attack: "Damage" # Can also translate as "Attack"
+    attack: "Saldırı" # Can also translate as "Attack"
     health: "Sağlık"
     speed: "Hız"
-#    skills: "Skills"
-
-#  save_load:
-#    granularity_saved_games: "Saved"
-#    granularity_change_history: "History"
+    skills: "Yetenekler"
+
+  save_load:
+    granularity_saved_games: "Kaydedildi"
+    granularity_change_history: "Geçmiş"

   options:
-#    general_options: "General Options" # Check out the Options tab in the Game Menu while playing a level
-#    volume_label: "Volume"
-#    music_label: "Music"
-#    music_description: "Turn background music on/off."
-#    autorun_label: "Autorun"
-#    autorun_description: "Control automatic code execution."
-#    editor_config: "Editor Config"
-#    editor_config_title: "Editor Configuration"
-#    editor_config_level_language_label: "Language for This Level"
-#    editor_config_level_language_description: "Define the programming language for this particular level."
-#    editor_config_default_language_label: "Default Programming Language"
-#    editor_config_default_language_description: "Define the programming language you want to code in when starting new levels."
-#    editor_config_keybindings_label: "Key Bindings"
+    general_options: "Genel Seçenekler" # Check out the Options tab in the Game Menu while playing a level
+    volume_label: "Ses"
+    music_label: "Müzik"
+    music_description: "Arkaplan müziğini aç/kapat."
+    autorun_label: "Otomatik çalıştır"
+    autorun_description: "Otomatik kod çalıştırmayı denetle."
+    editor_config: "Düzenleyici Yapılandırması"
+    editor_config_title: "Düzenleyici Yapılandırması"
+    editor_config_level_language_label: "Bu Bölümün Dili"
+    editor_config_level_language_description: "Bu bölüme özel olarak programlama dilini tanımlayın."
+    editor_config_default_language_label: "Öntanımlı Programlama Dili"
+    editor_config_default_language_description: "Yeni bölümlere başlarken kodlamada kullanılacak programlama dilini tanımla."
+    editor_config_keybindings_label: "Tuş Atamaları"
     editor_config_keybindings_default: "Varsayılan (Ace)"
 #    editor_config_keybindings_description: "Adds additional shortcuts known from the common editors."
 #    editor_config_livecompletion_label: "Live Autocompletion"
@@ -345,17 +345,17 @@
 #    press_title: "Bloggers/Press"
 #    press_paragraph_1_prefix: "Want to write about us? Feel free to download and use all of the resources included in our"
 #    press_paragraph_1_link: "press packet"
-#    press_paragraph_1_suffix: ". All logos and images may be used without contacting us directly."
-#    team: "Team"
-#    george_title: "CEO"
+    press_paragraph_1_suffix: ". Tüm logolar bizimle iletişime geçilmeden kullanılabilir."
+    team: "Takım"
+    george_title: "CEO"
 #    george_blurb: "Businesser"
-#    scott_title: "Programmer"
+    scott_title: "Programcı"
 #    scott_blurb: "Reasonable One"
-#    nick_title: "Programmer"
+    nick_title: "Programcı"
 #    nick_blurb: "Motivation Guru"
-#    michael_title: "Programmer"
+    michael_title: "Programcı"
 #    michael_blurb: "Sys Admin"
-#    matt_title: "Programmer"
+    matt_title: "Programcı"
 #    matt_blurb: "Bicyclist"

   versions:
@@ -397,10 +397,10 @@
     email_announcements_description: "CodeCombat ile ilgili son haberlere ve gelişmelere ulaşın."
     email_notifications: "Bilgilendirme"
 #    email_notifications_summary: "Controls for personalized, automatic email notifications related to your CodeCombat activity."
-#    email_any_notes: "Any Notifications"
+    email_any_notes: "Tüm Bildirimler"
 #    email_any_notes_description: "Disable to stop all activity notification emails."
 #    email_news: "News"
-#    email_recruit_notes: "Job Opportunities"
+    email_recruit_notes: "İş Fırsatları"
 #    email_recruit_notes_description: "If you play really well, we may contact you about getting you a (better) job."
     contributor_emails: "İştirakçi Sınıfı E-postaları"
     contribute_prefix: "Partimize katılmak üzere sizleri bekliyoruz ! Detaylı bilgi için "
@@ -414,8 +414,8 @@
 #    job_profile: "Job Profile" # Rest of this section (the job profile stuff and wizard stuff) is deprecated
 #    job_profile_approved: "Your job profile has been approved by CodeCombat. Employers will be able to see it until you either mark it inactive or it has not been changed for four weeks."
 #    job_profile_explanation: "Hi! Fill this out, and we will get in touch about finding you a software developer job."
-#    sample_profile: "See a sample profile"
-#    view_profile: "View Your Profile"
+    sample_profile: "Örnek bir profil gör"
+    view_profile: "Kendi Profilinize Bakın"
     wizard_tab: "Sihirbaz"
     wizard_color: "Sihirbaz Kıyafeti Rengi"

@@ -440,8 +440,8 @@
 #    maximize_editor: "Maximize/minimize code editor."
 #    move_wizard: "Move your Wizard around the level."

-#  community:
-#    main_title: "CodeCombat Community"
+  community:
+    main_title: "CodeCombat Topluluğu"
 #    introduction: "Check out the ways you can get involved below and decide what sounds the most fun. We look forward to working with you!"
 #    level_editor_prefix: "Use the CodeCombat"
 #    level_editor_suffix: "to create and edit levels. Users have created levels for their classes, friends, hackathons, students, and siblings. If create a new level sounds intimidating you can start by forking one of ours!"
@@ -503,7 +503,7 @@
     level_tab_thangs_add: "Nesne Ekle"
     delete: "Sil"
     duplicate: "Kopyala"
-#    rotate: "Rotate"
+    rotate: "Döndür"
     level_settings_title: "Ayarlar"
     level_component_tab_title: "Geçerli Bileşenler"
     level_component_btn_new: "Yeni Bileşen Oluştur"
@@ -519,18 +519,18 @@
     create_system_title: "Yeni Sistem Oluştur"
     new_component_title: "Yeni Bileşen Oluştur"
     new_component_field_system: "Sistem"
-#    new_article_title: "Create a New Article"
+    new_article_title: "Yeni Bir Makale Oluştur"
 #    new_thang_title: "Create a New Thang Type"
     new_level_title: "Yeni Bir Seviye Oluştur"
 #    new_article_title_login: "Log In to Create a New Article"
 #    new_thang_title_login: "Log In to Create a New Thang Type"
 #    new_level_title_login: "Log In to Create a New Level"
-#    new_achievement_title: "Create a New Achievement"
+    new_achievement_title: "Yeni Bir Başarı Oluştur"
 #    new_achievement_title_login: "Log In to Create a New Achievement"
 #    article_search_title: "Search Articles Here"
 #    thang_search_title: "Search Thang Types Here"
     level_search_title: "Seviye ara"
-#    achievement_search_title: "Search Achievements"
+    achievement_search_title: "Başarı Ara"
 #    read_only_warning2: "Note: you can't save any edits here, because you're not logged in."
 #    no_achievements: "No achievements have been added for this level yet."
 #    achievement_query_misc: "Key achievement off of miscellanea"
@@ -542,7 +542,7 @@
     edit_article_title: "Makaleyi Düzenle"

   contribute:
-#    page_title: "Contributing"
+    page_title: "Katkıda Bulunma"
     character_classes_title: "Karakter Sınıfları"
 #    introduction_desc_intro: "We have high hopes for CodeCombat."
 #    introduction_desc_pref: "We want to be where programmers of all stripes come to learn and play together, introduce others to the wonderful world of coding, and reflect the best parts of the community. We can't and don't want to do that alone; what makes projects like GitHub, Stack Overflow and Linux great are the people who use them and build on them. To that end, "
@@ -627,24 +627,24 @@
 #    helpful_ambassadors: "Our Helpful Ambassadors:"

   ladder:
-#    please_login: "Please log in first before playing a ladder game."
-#    my_matches: "My Matches"
-#    simulate: "Simulate"
-#    simulation_explanation: "By simulating games you can get your game ranked faster!"
-#    simulate_games: "Simulate Games!"
-#    simulate_all: "RESET AND SIMULATE GAMES"
+    please_login: "Bir sıralama oyunu oynamadan önce lütfen oturum açın."
+    my_matches: "Karşılaşmalarım"
+    simulate: "Simüle Et"
+    simulation_explanation: "Oyunları simüle ederek oyun sıralamanızı daha hızlı edinebilirsiniz!"
+    simulate_games: "Oyunları Simüle Et!"
+    simulate_all: "SIFIRLA VE OYUNLARI SİMÜLE E"
 #    games_simulated_by: "Games simulated by you:"
 #    games_simulated_for: "Games simulated for you:"
-#    games_simulated: "Games simulated"
-#    games_played: "Games played"
-#    ratio: "Ratio"
+    games_simulated: "Simüle edilen oyunlar"
+    games_played: "Oynanan oyunlar"
+    ratio: "Oran"
     leaderboard: "Sıralama"
 #    battle_as: "Battle as "
-    summary_your: "Senin "
-#    summary_matches: "Matches - "
-#    summary_wins: " Wins, "
-#    summary_losses: " Losses"
-#    rank_no_code: "No New Code to Rank"
+    summary_your: "Sizin "
+    summary_matches: "Maçlarınız - "
+    summary_wins: " Galibiyet, "
+    summary_losses: " Mağlubiyet"
+    rank_no_code: "Sıralamak Üzere Yeni Kod Yok"
     rank_my_game: "Oyunumu Derecelendir!"
     rank_submitting: "Kayıt Ediliyor..."
 #    rank_submitted: "Submitted for Ranking"
@@ -655,15 +655,15 @@
 #    code_being_simulated: "Your new code is being simulated by other players for ranking. This will refresh as new matches come in."
 #    no_ranked_matches_pre: "No ranked matches for the "
 #    no_ranked_matches_post: " team! Play against some competitors and then come back here to get your game ranked."
-#    choose_opponent: "Choose an Opponent"
-#    select_your_language: "Select your language!"
-#    tutorial_play: "Play Tutorial"
-#    tutorial_recommended: "Recommended if you've never played before"
-#    tutorial_skip: "Skip Tutorial"
-#    tutorial_not_sure: "Not sure what's going on?"
-#    tutorial_play_first: "Play the Tutorial first."
-#    simple_ai: "Simple AI"
-#    warmup: "Warmup"
+    choose_opponent: "Bir Rakip Seçin"
+    select_your_language: "Dilinizi seçin!"
+    tutorial_play: "Öğreticiyi Oyna"
+    tutorial_recommended: "Hiç oynamadıysanız önerilir"
+    tutorial_skip: "Öğreticiyi Atla"
+    tutorial_not_sure: "Ne olduğundan emin değil misiniz?"
+    tutorial_play_first: "İlk olarak Öğreticiyi oynayın."
+    simple_ai: "Basit YZ"
+    warmup: "Isınma"
 #    friends_playing: "Friends Playing"
 #    log_in_for_friends: "Log in to play with your friends!"
 #    social_connect_blurb: "Connect and play against your friends!"
@@ -711,9 +711,9 @@
 #    left_xp_infix: " until level "
 #    left_xp_postfix: ""

-#  account:
-#    recently_played: "Recently Played"
-#    no_recent_games: "No games played during the past two weeks."
+  account:
+    recently_played: "En Son Oynananlar"
+    no_recent_games: "Son iki hafta içerisinde hiç oyun oynanmadı."

   loading_error:
     could_not_load: "Yüklenemiyor"
@@ -723,13 +723,13 @@
     not_found: "Bulunamadı."
     not_allowed: "Yönteme izin verilmiyor."
     timeout: "Sunucu zamanaşımı."
-#    conflict: "Resource conflict."
-#    bad_input: "Bad input."
+    conflict: "Kaynak çakışması."
+    bad_input: "Hatalı girdi."
     server_error: "Sunucu hatası."
     unknown: "Bilinmeyen hata."

   resources:
-#    sessions: "Sessions"
+    sessions: "Oturumlar"
     your_sessions: "Oturumlarınız"
     level: "Seviye"
     social_network_apis: "Sosyal Ağ API'leri"


Update uk.coffee (#5793)

Update uk.coffee (#5793)

* Updated uk.coffee

* Update uk.coffee

* Update uk.coffee

Co-authored-by: Matias Forbord <7d4dc3970a879d64548b3a155f22f7990dc8fc0c@users.noreply.github.com>
Co-authored-by: Valentin Bryukhanov <53545457af642b0f8712bb2cc004f120ce4436f4@gmail.com>

--- app/locale/uk.coffee
+++ app/locale/uk.coffee
@@ -234,8 +234,8 @@
 #    video_title: "__video__ | Video Level"
 #    game_development_title: "__level__ | Game Development"
 #    web_development_title: "__level__ | Web Development"
-#    anon_signup_title_1: "CodeCombat has a"
-#    anon_signup_title_2: "Classroom Version!"
+    anon_signup_title_1: "CodeCombat має"
+    anon_signup_title_2: "Класний Версію!"
 #    anon_signup_enter_code: "Enter Class Code:"
 #    anon_signup_ask_teacher: "Don't have one? Ask your teacher!"
 #    anon_signup_create_class: "Want to create a class?"
@@ -649,7 +649,7 @@
     tome_available_spells: "Доступні заклинання"
     tome_your_skills: "Ваші вміння"
     hints: "НАТЯКИ (Підказки)"
-#    videos: "Videos"
+    videos: "Відео"
     hints_title: "Натяк (підказка) {{number}}"
     code_saved: "Код збережено"
     skip_tutorial: "Пропустити (esc)"
@@ -1087,7 +1087,7 @@
   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."
-#    learn_more: "Learn More"
+    learn_more: "Дізнатися більше"
     main_title: "Якщо ви хочете навчитися програмувати, вам потрібно писати код (багато коду)."
     main_description: "Тут, на CodeCombat, наше завдання - переконатися, що ви це робитимете з усмішкою на обличчі."
     mission_link: "Місія"
@@ -1253,14 +1253,14 @@
     college_plus: "Коледж/університет чи вище"
     referrer: "Як ви про нас почули?"
     referrer_help: "Наприклад: від іншого вчителя, ваших учнів, на конференції, Code.org тощо."
-#    referrer_default: "Select One"
+    referrer_default: "Виберіть відповідь"
 #    referrer_conference: "Conference (e.g. ISTE)"
 #    referrer_hoc: "Code.org/Hour of Code"
 #    referrer_teacher: "A teacher"
 #    referrer_admin: "An administrator"
 #    referrer_student: "A student"
 #    referrer_pd: "Professional trainings/workshops"
-#    referrer_web: "Google"
+    referrer_web: "Google"
 #    referrer_other: "Other"
     anything_else: "Ще щось, про що нам варто знати?" # {change}
 #    anything_else_helper: ""
@@ -1483,7 +1483,7 @@
     switch_to: "Переключитись на"
     greetings: "Вітання!"
     back_classrooms: "Повернутись до моїх класів"
-    back_classroom: "Повернутися до класу"
+    back_classroom: "Повернутись до класу"
     back_courses: "Повернутись до моїх курсів"
     edit_details: "Редагувати деталі класу"
     purchase_enrollments: "Придбати учнівські ліцензії"
@@ -1726,7 +1726,7 @@
     enroll_student: "Зарахувати учня"
     apply_license: "Застосувати ліцензію"
     revoke_license: "Відкликати ліцензію"
-#    revoke_licenses: "Revoke All Licenses"
+    revoke_licenses: "Відкликати всі ліцензіїс"
     course_progress: "Прогрес курсу"
     not_applicable: "N/A"
     edit: "редагувати"
@@ -1943,7 +1943,7 @@
     revoking: "Відкликаємо..."
 #    unused_licenses: "You have unused Licenses that allow you to assign students paid courses when they're ready to learn more!"
 #    remember_new_courses: "Remember to assign new courses!"
-#    more_info: "More Info"
+    more_info: "Більше інформації"
 #    how_to_assign_courses: "How to Assign Courses"
 #    select_students: "Select Students"
 #    select_instructions: "Click the checkbox next to each student you want to assign courses to."


Fixed wizard setting things.

Fixed wizard setting things.

--- app/locale/zh.coffee
+++ app/locale/zh.coffee
@@ -130,7 +130,7 @@
 #    learn_more: "Learn more about being a Diplomat"
 #    subscribe_as_diplomat: "Subscribe as a Diplomat"

-#  wizard_settings:
+  wizard_settings:
     title: "巫师设定"
     customize_avatar: "设置你的头像"
     active: "启用"


lots of stuff. IMAGES!

lots of stuff. IMAGES!

--- src/js/app.coffee
+++ src/js/app.coffee
@@ -2,195 +2,27 @@
 Settings = require('settings')
 ajax = require('ajax')
 async = require('async')
-require 'Object.observe.poly'
+Emitter = require('emitter')

 CONFIG_BASE_URL = 'http://bobby.alessiobogon.com:8020/'
+ICON_MENU_UNCHECKED = 'images/icon_menu_unchecked.png'
+ICON_MENU_CHECKED = 'images/icon_menu_checked.png'

 console.log "accessToken: #{Settings.option 'accessToken'}"

 mainMenu = undefined
 signInWindow = undefined
 detailedItemCard = undefined
-mainMenu = undefined
-
-model =
-  shows: undefined
-
-
-getToWatchList = (showList, callback) ->
-  showListUpdated = showList[..]
-  async.each(
-    showListUpdated
-    (item, doneItem) ->
-      traktvRequest(
-        "https://api-v2launch.trakt.tv/shows/#{item.show.ids.trakt}/progress/watched"
-        (response, status, req) ->
-          # console.log "getToWatchList: asked "
-          # console.log "returned: #{JSON.stringify response.seasons}"
-          if status != 200
-            doneItem(response: response, status: status, req: req)
-          item.next_episode = response.next_episode
-          item.seasons = response.seasons
-          for season in item.seasons
-            for episode in season.episodes
-              episode.completed ?= false
-          doneItem()
-      )
-    (err) ->
-      if err?
-        console.log "Failed response (#{err.status}): #{err.response}"
-        console.log "Request was: #{err.req}"
-      console.log "getToWatchList returning"
-      callback(showListUpdated)
-  )
-
-displaySignInWindow = ->
-  signInWindow = new UI.Card(
-    title: 'Sign-in required'
-    body: 'Open the Pebble App and configure Pebble Shows.'
-  )
-  signInWindow.on 'click', 'back', ->
-    # No escape :)
-    return
-  signInWindow.show()
-  return
-
-refreshModels = ->
- traktvRequest 'sync/watched/shows', (response, status, req) ->
-    shows = response
-    # console.log "Returned shows: #{JSON.stringify shows.map (e)->e.show}"
-    getToWatchList shows, (toWatchList) ->
-      console.log 'toWatchList updated'
-      model.shows = toWatchList
-
-checkWatched = (episode, success, failure) ->
-  # console.log("Check watched! episode: #{JSON.stringify(episode)}")
-  request =
-    shows: [
-      title: episode.show.title
-      year: episode.show.year
-      ids: episode.show.ids
-      seasons: [
-        number: episode.season
-        episodes: [
-          number: episode.episode
-        ]
-      ]
-    ]
-  console.log "request: #{JSON.stringify request}"
-  traktvRequest
-    url: 'https://api-v2launch.trakt.tv/sync/history'
-    method: 'POST'
-    data: request
-    (response, status, req) ->
-      console.log "Check SUCCESS"
-      # console.log "#{index}: #{key}: #{value}" for key, value of index for index in model.shows
-      itemToMark = item for item in model.shows when item.show.ids.trakt == episode.show.ids.trakt
-      seasonToMark = season for season in itemToMark.seasons when season.number == episode.season
-      console.log "season to mark: #{JSON.stringify seasonToMark}"
-      episodeToMark = ep for ep in seasonToMark.episodes when ep.number == episode.episode
-      console.log "ep to mark: #{JSON.stringify episodeToMark}"
-      episodeToMark.completed = true
-      # console.log "found show. #{key}, #{value}" for key, value of show
-      # console.log "I must update loca show #{show.title}"
-      success()
-    (response, status, req) ->
-      console.log "Check FAILURE"
-      failure(response, status, req)
-
-
-compareByKey = (key) ->
-  (a, b) ->
-    -1 if a[key] < b[key]
-    0 if a[key] == b[key]
-    1 if a[key] > b[key]
-
-# {
-#   episode: 12
-#   season: 2
-# } or undefined
-firstUnwatchedEpisode = (show) ->
-  seasons = show.seasons[..]
-  seasons.sort compareByKey('number')
-  for season in seasons
-    # console.log "considering #{JSON.stringify season}"
-    episodes = season.episodes[..]
-    episodes.sort(compareByKey('number'))
-    for episode in episodes
-      unless episode.completed
-        return {
-          episode: episode.number
-          season: season.number
-        }
-
-
-displayToWatchMenu = ->
-  unless model.shows?
-    observer = (changes) ->
-      unless 'shows' in (change.name for change in changes)
-        return
-
-      console.log 'called observe. this is ', JSON.stringify(this)
-      displayToWatchMenu()
-      Object.unobserve model.shows, observer
-
-    Object.observe model, observer
-    return;
-
-  getToWatchMenuItems = ->
-    toWatch = []
-    for item in model.shows
-      # console.log "Title: #{item.show.title}"
-      ep = firstUnwatchedEpisode(item)
-      # console.log "firstUnwatchedEpisode: #{JSON.stringify ep}}"
-      if ep?
-        toWatch.push
-          title: item.show.title
-          subtitle: "Season #{ep.season} Ep. #{ep.episode}"
-          data:
-            episode: ep.episode
-            season: ep.season
-            show: item.show
-    toWatch
-
-  toWatchMenu = new UI.Menu(
-    sections: [{
-      items: getToWatchMenuItems()
-      }]
-    )
-  checkHandler = (e) ->
-    console.log "Deleting menu item [#{e.sectionIndex}, #{e.itemIndex}]"
-    toWatchMenu.items(0, getToWatchMenuItems())
-
-  toWatchMenu.on 'longSelect', (e) ->
-    e.item.subtitle = "Checking in..."
-    toWatchMenu.item(e.sectionIndex, e.itemIndex, e.item)
-    checkWatched e.item.data, ->
-      console.log "End of checkWatched. needt to remove"
-      console.log "#{key}: #{value}" for key, value of e
-      checkHandler(e)
-
-  toWatchMenu.on 'select', (e) ->
-    episode = e.item.data
-    traktvRequest(
-      "https://api-v2launch.trakt.tv/shows/#{episode.show.ids.trakt}/seasons/#{episode.season}/episodes/#{episode.episode}",
-      (response, status, req) ->
-        detailedItemCard = new UI.Card(
-          title: episode.show.title
-          subtitle: "Season #{episode.season} Ep. #{episode.episode}"
-          body: "Title: #{response.title}"
-          style: 'small'
-        )
-        detailedItemCard.on('longClick', 'select', ->
-          e.item.subtitle = "Checking in..."
-          toWatchMenu.item(e.sectionIndex, e.itemIndex, e.item)
-          detailedItemCard.hide()
-          checkWatched episode, -> checkHandler(e)
-        )
-        detailedItemCard.show()
-    )
-  toWatchMenu.show()
-  return
+
+updatesEmitter = new Emitter()
+
+shows = undefined
+
+sleep = (ms) ->
+  unixtime_ms = new Date().getTime();
+  while(new Date().getTime() < unixtime_ms + ms)
+    1
+

 traktvRequest = (opt, success, failure) ->
   console.log "traktvRequest: opt: #{JSON.stringify opt}"
@@ -236,16 +68,273 @@
       console.log "Request failure (#{status} #{method} #{url})"
       failure(response, status, req)

-
-# Set a configurable with the open callback
-Settings.config {
-  url: CONFIG_BASE_URL
-  autoSave: true
-}, (e) ->
-  signInWindow.hide()
-  refreshModels()
-
-refreshModels()
+reloadShow = (showID, success, failure) ->
+  traktvRequest(
+    "https://api-v2launch.trakt.tv/shows/#{showID}/progress/watched"
+    (response, status, req) ->
+      console.log "Reloading show #{showID}"
+      item = i for i in shows when i.show.ids.trakt == showID
+
+      item.next_episode = response.next_episode
+      item.seasons = response.seasons
+      success(item) if success?
+    failure if failure?
+  )
+
+
+
+getToWatchList = (showList, callback) ->
+  showListUpdated = showList[..]
+  async.each(
+    showListUpdated
+    (item, doneItem) ->
+      traktvRequest(
+        "https://api-v2launch.trakt.tv/shows/#{item.show.ids.trakt}/progress/watched"
+        (response, status, req) ->
+          # console.log "getToWatchList: asked "
+          # console.log "returned: #{JSON.stringify response.seasons}"
+          if status != 200
+            doneItem(response: response, status: status, req: req)
+          item.next_episode = response.next_episode
+          item.seasons = response.seasons
+          # for season in item.seasons
+          #   for episode in season.episodes
+          #     episode.completed ?= false
+          doneItem()
+      )
+    (err) ->
+      if err?
+        console.log "Failed response (#{err.status}): #{err.response}"
+        console.log "Request was: #{err.req}"
+      # console.log "getToWatchList returning"
+      callback(showListUpdated) if callback?
+  )
+
+displaySignInWindow = ->
+  signInWindow = new UI.Card(
+    title: 'Sign-in required'
+    body: 'Open the Pebble App and configure Pebble Shows.'
+  )
+  signInWindow.on 'click', 'back', ->
+    # No escape :)
+    return
+  signInWindow.show()
+
+isNextEpisodeForItemAired = (item) ->
+  return false unless item.next_episode?
+  if item.next_episode.season > item.seasons.length
+    return false
+  season = s for s in item.seasons when s.number == item.next_episode.season
+  if item.next_episode.number > season.aired
+    return false
+  true
+
+refreshModels = ->
+  traktvRequest 'sync/watched/shows', (response, status, req) ->
+    # console.log "Returned shows: #{JSON.stringify shows.map (e)->e.show}"
+    getToWatchList response, (toWatchList) ->
+      console.log 'toWatchList updated'
+      shows = toWatchList
+      updatesEmitter.emit 'update', 'shows', shows
+
+modifyCheckState = (opt, success, failure) ->
+  # console.log("Check watched! episode: #{JSON.stringify(episode)}")
+  console.log "checkWatched: opt: #{JSON.stringify opt}"
+  if opt.episodeNumber? and not opt.seasonNumber?
+    failure()
+    return
+
+  opt.completed ?= true
+
+  request =
+    shows: [
+      ids: trakt: opt.showID
+      seasons: [{
+        number: opt.seasonNumber
+        episodes: [{
+          number: opt.episodeNumber
+        }] if opt.episodeNumber
+      }] if opt.seasonNumber
+    ]
+
+  url = 'https://api-v2launch.trakt.tv/sync/history'
+  url += '/remove' if opt.completed == false
+
+  # console.log "request: #{JSON.stringify request}"
+  traktvRequest
+    url: url
+    method: 'POST'
+    data: request
+    (response, status, req) ->
+      console.log "Check succeeded: #{JSON.stringify request}"
+      # console.log "#{index}: #{key}: #{value}" for key, value of index for index in shows
+      for item in shows when item.show.ids.trakt == opt.showID
+        for season in item.seasons when not opt.seasonNumber? or season.number == opt.seasonNumber
+          for episode in season.episodes when not opt.episodeNumber? or episode.number == opt.episodeNumber
+            console.log "Marking as seen #{item.show.title} S#{season.number}E#{episode.number}"
+            episode.completed = opt.completed
+      success()
+    (response, status, req) ->
+      console.log "Check FAILURE"
+      failure(response, status, req)
+
+
+compareByKey = (key) ->
+  (a, b) ->
+    -1 if a[key] < b[key]
+    0 if a[key] == b[key]
+    1 if a[key] > b[key]
+
+# {
+#   episode: 12
+#   season: 2
+# } or undefined
+firstUnwatchedEpisode = (show) ->
+  seasons = show.seasons[..]
+  seasons.sort compareByKey('number')
+  for season in seasons
+    # console.log "considering #{show.show.title}, #{JSON.stringify seasons}"
+    episodes = season.episodes[..]
+    episodes.sort(compareByKey('number'))
+    for episode in episodes
+      unless episode.completed == true
+        return {
+          episodeNumber: episode.number
+          seasonNumber: season.number
+        }
+
+
+displayToWatchMenu = (callback) ->
+  unless shows?
+    handler = (e) ->
+      updatesEmitter.off 'updates', 'shows', handler
+      displayToWatchMenu()
+    updatesEmitter.on 'update', 'shows', handler
+    return
+
+  getToWatchMenuItems = ->
+    toWatch = []
+    for item in shows
+      # console.log "Title: #{item.show.title}"
+      ep = firstUnwatchedEpisode(item)
+      ep_s = if ep? then "S#{ep.seasonNumber}E#{ep.episodeNumber}" else "undefined"
+      console.log "First unwatched episode for #{item.show.title} is #{ep_s}"
+      # console.log "firstUnwatchedEpisode: #{JSON.stringify ep}}"
+      if ep?
+        toWatch.push
+          title: item.show.title
+          subtitle: "Season #{ep.seasonNumber} Ep. #{ep.episodeNumber}"
+          icon: ICON_MENU_UNCHECKED
+          data:
+            episodeNumber: ep.episodeNumber
+            seasonNumber: ep.seasonNumber
+            showID: item.show.ids.trakt
+    toWatch
+
+  # console.log "item: #{key}: #{value}" for key, value of item for item in shows
+  # console.log "data: #{JSON.stringify data}"
+  createToWatchMenuItem = (opt) ->
+    for key in ['showID', 'episodeTitle', 'seasonNumber', 'episodeNumber', 'completed']
+      unless opt[key]?
+        console.log "ERROR: #{key} not in #{JSON.stringify opt}"
+        return
+    {
+      title: opt.episodeTitle
+      subtitle: "Season #{opt.seasonNumber} Ep. #{opt.episodeNumber}"
+      icon: if opt.completed then ICON_MENU_CHECKED else ICON_MENU_UNCHECKED
+      data:
+        showID: opt.showID
+        episodeNumber: opt.episodeNumber
+        seasonNumber: opt.seasonNumber
+        completed: opt.completed
+        isNextEpisodeListed: opt.isNextEpisodeListed
+    }
+
+  toWatchMenu = new UI.Menu
+    sections:
+      {
+        title: item.show.title
+        items: [
+          createToWatchMenuItem(
+            showID: item.show.ids.trakt
+            episodeTitle: item.next_episode.title
+            seasonNumber: item.next_episode.season
+            episodeNumber: item.next_episode.number
+            completed: false
+          )
+        ]
+      } for item in shows when isNextEpisodeForItemAired(item)
+
+  toWatchMenu.on 'longSelect', (e) ->
+    data = e.item.data
+    modifyCheckState
+      showID: data.showID
+      episodeNumber: data.episodeNumber
+      seasonNumber: data.seasonNumber
+      completed: not e.item.completed
+      () ->
+        element = e.item
+        isNowCompleted = not element.data.completed
+
+        if isNowCompleted
+          element.data.completed = true
+          element.icon = ICON_MENU_CHECKED
+        else
+          element.data.completed = false
+          element.icon = ICON_MENU_UNCHECKED
+
+        toWatchMenu.item(e.sectionIndex, e.itemIndex, element)
+
+        if isNowCompleted and not element.isNextEpisodeListed
+          reloadShow data.showID, (reloadedShow) ->
+            console.log "RELOADED ShowID: #{reloadedShow.show.ids.trakt}, title: #{reloadedShow.show.title}"
+            if isNextEpisodeForItemAired(reloadedShow) and not element.isNextEpisodeListed
+              element.isNextEpisodeListed = true
+
+              newItem = createToWatchMenuItem(
+                showID: data.showID
+                episodeTitle: reloadedShow.next_episode.title
+                seasonNumber: reloadedShow.next_episode.season
+                episodeNumber: reloadedShow.next_episode.number
+                completed: false
+              )
+              console.log "toWatchMenu.item(#{e.sectionIndex}, #{e.section.items.length}, #{JSON.stringify newItem}"
+
+              toWatchMenu.item(e.sectionIndex, e.section.items.length, newItem)
+
+  toWatchMenu.on 'select', (e) ->
+    episode = e.item.data
+    traktvRequest(
+      "https://api-v2launch.trakt.tv/shows/#{episode.show.ids.trakt}/seasons/#{episode.season}/episodes/#{episode.episode}",
+      (response, status, req) ->
+        detailedItemCard = new UI.Card(
+          title: episode.show.title
+          subtitle: "Season #{episode.season} Ep. #{episode.episode}"
+          body: "Title: #{response.title}"
+          style: 'small'
+        )
+        # detailedItemCard.on('longClick', 'select', ->
+        #   e.item.subtitle = "Checking in..."
+        #   toWatchMenu.item(e.sectionIndex, e.itemIndex, e.item)
+        #   detailedItemCard.hide()
+        #   checkWatched episode, -> checkHandler(e)
+        # )
+        detailedItemCard.show()
+    )
+  toWatchMenu.show()
+
+  callback() if callback?
+
+initSettings = ->
+  Settings.init()
+  Settings.config {
+    url: CONFIG_BASE_URL
+    autoSave: true
+  }, (e) ->
+    signInWindow.hide()
+    refreshModels()
+
+initSettings()

 mainMenu = new UI.Menu
   sections: [
@@ -264,13 +353,20 @@
 mainMenu.on 'select', (e) ->
   switch e.item.id
     when 'toWatch'
-      displayToWatchMenu()
+      e.item.subtitle = "Loading..."
+      mainMenu.item(e.sectionIndex, e.itemIndex, e.item)
+      displayToWatchMenu ->
+        e.item.subtitle = undefined
+        mainMenu.item(e.sectionIndex, e.itemIndex, e.item)
     when 'advanced'
       advancedMenu = new UI.Menu
         sections: [
           items: [{
             title: 'Reset localStorage'
-            action: -> localStorage.clear()
+            action: ->
+              localStorage.clear()
+              initSettings()
+              console.log "Local storage cleared"
           }, {
             title: 'Refresh'
             action: -> refreshModels()
@@ -280,4 +376,4 @@
       advancedMenu.show()

 mainMenu.show()
-
+refreshModels()


Assign search query to new object instead of props

--- app/talk/search.cjsx
+++ app/talk/search.cjsx
@@ -49,7 +49,7 @@
       page: 1
       page_size: 10

-    paramsToUse = Object.assign defaultParams, params
+    paramsToUse = Object.assign {}, defaultParams, params

     talkClient.type('searches').get(paramsToUse).then (searches) =>
       @setState
@@ -64,7 +64,7 @@
     @goToPage page

   goToPage: (n) ->
-    nextQuery = Object.assign @props.query, {page: n}
+    nextQuery = Object.assign {}, @props.query, {page: n}

     @transitionTo location.pathname, @props.params, nextQuery


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -140,10 +140,10 @@

   # let's print out the colors on a canvas to see what they look like
   canvas.font('14px Helvetica')
-  for color, index in [individualRgb, arrayRgb, hex, rgb, hsl, anotherColor, namedBrown]
+  for color, index in ['individualRgb', 'arrayRgb', 'hex', 'rgb', 'hsl', 'anotherColor', 'namedBrown']
     canvas.centerText
-      color: color
-      text: "Look at me"
+      color: eval(color)
+      text: color
       y: 20 * (index + 1)

   </pre></code>


add class based on proximity

add class based on proximity

--- assets/js/app.coffee
+++ assets/js/app.coffee
@@ -1,17 +1,14 @@
     angular.module('test', []).controller('TestController', ($scope, $http, $timeout) ->
       tick = ->
         $http.get('/stack').then( (data) ->
+#          $scope.beacon_stack = data.data
           $scope.beacon_stack = data.data.filter (beacon) ->
             beacon.minor == 1
           for beacon in $scope.beacon_stack
-            if beacon.rssi < -65
-              beacon.table_class = "error"
+            if beacon.proximity == "near"
+              beacon.table_class = "inside"
             else
-              beacon.table_class = "positive"
-#            if beacon.proximity == "near"
-#              beacon.table_class = "error"
-#            else if beacon.proximity == "immediate"
-#              beacon.table_class = "positive"
+              beacon.table_class = "outside"
           $timeout tick, 1000
         )
       tick()


add host in config and use 0.0.0.0 as default

--- src/index.coffee
+++ src/index.coffee
@@ -12,6 +12,7 @@
 var server = new Eurekapi({
     name: 'projectName',
     version: 1,
+    host: '0.0.0.0'
     port: 4000,
     enableCORS: false,
     database: {
@@ -136,8 +137,9 @@
             port = @config.port
         unless port
             port = @config.port
-        console.log "starting http://localhost:#{port}..."
-        console.log "api accessible at http://localhost:#{port}#{@baseURI}"
+        host = @config.host or "0.0.0.0"
+        console.log "starting http://#{host}:#{port}..."
+        console.log "api accessible at http://#{host}:#{port}#{@baseURI}"

         @server = require('http').createServer(@app)
         @server.listen port, callback


* rename _decodeValue to stringToValue * rename _encodeValue to valueToString

--- src/index.coffee
+++ src/index.coffee
@@ -46,10 +46,11 @@
     return
   assign: (aValue, aOptions)->
     checkValidity = aOptions.checkValidity if aOptions
+    vType  = @$type
     if aValue instanceof Value
       aValue = aValue.valueOf()
-    else if @$type._decodeValue
-      aValue = @$type._decodeValue aValue
+    else if vType.stringToValue and isString(aValue)
+      aValue = vType.stringToValue aValue
     @$type.validate(aValue, checkValidity) if checkValidity isnt false
     @_assign aValue
     @
@@ -72,19 +73,17 @@
   # assign value from JSON string.
   fromJson: (aString)->
     aString = JSON.parse aString
-    decode = @$type._decodeValue
-    aString = decode aString if decode
     @assign aString
   # create a new value object from JSON string.
   createFromJson: (aString)->
     aString = JSON.parse aString
-    decode = @$type._decodeValue
-    aString = decode aString if decode
-    createObject @$type.ValueType, aString, @$type
+    vType  = @$type
+    aString = vType.stringToValue aString if vType.stringToValue
+    createObject @$type.ValueType, aString, vType
   toJSON: (aOptions)->
     result = @toObject(aOptions)
-    encode = @$type._encodeValue
-    result = encode result if encode
+    vType  = @$type
+    result = vType.valueToString result if vType.valueToString
     result
   toJson: (aOptions)->
     result = @toJSON(aOptions)


Update html directly

Update html directly

--- src/index.coffee
+++ src/index.coffee
@@ -1,8 +1,20 @@
 mdast = require 'mdast'
 uuid = require 'uuid'
-{DOMParser} = require 'xmldom'
+# {DOMParser} = require 'xmldom'

 $ = React.createElement
+HTMLWrapper = React.createClass
+  _update: ->
+    current = @props.html
+    if @_lastHtml isnt current
+      @_lastHtml = current
+      node = @refs.htmlWrapper.getDOMNode()
+      node.innerHTML = @props.html
+
+  componentDidUpdate: -> @_update()
+  componentDidMount: -> @_update()
+  render: -> $ 'div', ref: 'htmlWrapper'
+
 toChildren = (node, parentKey, tableAlign = []) ->
   return (for child, i in node.children
     align = tableAlign[i]
@@ -88,9 +100,7 @@
           dompurify.sanitize(node.value)
         else
           node.value
-      $ 'div', {key}, [
-        $ 'div', {key: key+'_raw', dangerouslySetInnerHTML:{__html: value}}
-      ]
+      $ HTMLWrapper, key: key, html: value
     else
       throw node.type + ' is unsuppoted node type. report to https://github.com/mizchi/md2react/issues'


Define droppable as an amd module

Define droppable as an amd module

--- src/index.coffee
+++ src/index.coffee
@@ -2,20 +2,26 @@
 Draggable = require("./draggable")
 Droppable = require("./droppable")

-$.fn.droppable = (options = {}) ->
-  values = for element in this
-    (new Droppable(element, options))
+factory = ($) ->
+  $.fn.droppable = (options = {}) ->
+    values = for element in this
+      (new Droppable(element, options))

-  values
+    values

-$.fn.draggable = (options = {}) ->
-  values = for element in this
-    (new Draggable(element, options))
+  $.fn.draggable = (options = {}) ->
+    values = for element in this
+      (new Draggable(element, options))

-  values
+    values

-$.fn.sortable = (options = {}) ->
-  values = for element in this
-    (new Sortable(element, options))
+  $.fn.sortable = (options = {}) ->
+    values = for element in this
+      (new Sortable(element, options))

-  values
+    values
+
+if typeof define == "function" && define.amd
+  define(["jquery"], factory)
+else
+  factory(jQuery)


dont use color on result

dont use color on result

--- src/index.coffee
+++ src/index.coffee
@@ -17,7 +17,7 @@
   addr = program.addr
   try
     result = zipcode.getZipCode(addr)
-    console.log clc.green(result)
+    console.log result
   catch e
     console.log clc.red("ERROR: #{e.message}")
     process.exit(1)


Minor change

Minor change

--- src/index.coffee
+++ src/index.coffee
@@ -11,7 +11,7 @@
   help: 'Quiver notebook or note file(s)'
 .option 'output',
   abbr: 'o'
-  help: 'Path to the output dir'
+  help: 'Path to the output directory'
 .option 'version',
   abbr: 'v'
   flag: true


small fix for when index === 0, it should work, as 0 || undefined; returns undefined not 0.

--- src/index.coffee
+++ src/index.coffee
@@ -29,7 +29,8 @@
         original = {}

         changes.forEach (change,i) ->
-          new_path = "#{base or ''}[#{change.index or change.name}]"
+          index_or_name = if change.index>-1 then change.index else change.name
+          new_path = "#{base or ''}[#{index_or_name}]"
           # console.log change
           part =
             path: new_path


work on Return and also Value, making progress...

--- src/nodes.coffee
+++ src/nodes.coffee
@@ -659,6 +659,7 @@
 # `true`, `false`, `null`...
 exports.Literal = class Literal extends Base
   constructor: (@value) ->
+    super()

   makeReturn: ->
     if @isStatement() then this else super
@@ -679,6 +680,9 @@
     return this if @value is 'continue' and not o?.loop

   compileNode: (o) ->
+    # Don't tun this code through @makeCode below....
+    return @icedCompileIced o if @icedLoopFlag and @icedIsJump()
+
     code = if @value is 'this'
       if o.scope.method?.bound then o.scope.method.context else @value
     else if @value.reserved
@@ -690,6 +694,23 @@

   toString: ->
     ' "' + @value + '"'
+
+  icedWalkAst : (parent, o) ->
+    if @value is 'arguments' and o.foundAwaitFunc
+      o.foundArguments = true
+      @value = "_arguments"
+    false
+
+  icedIsJump : -> @isStatement()
+
+  icedCompileIced: (o) ->
+    d =
+      'continue' : iced.const.c_while
+      'break'    : iced.const.b_while
+    l = d[@value]
+    func = new Value new Literal l
+    call = new Call func, []
+    return call.compileNode o

 class exports.Undefined extends Base
   isAssignable: NO
@@ -713,7 +734,9 @@
 # A `return` is a *pureStatement* -- wrapping it in a closure wouldn't
 # make sense.
 exports.Return = class Return extends Base
-  constructor: (expr) ->
+  constructor: (expr, auto) ->
+    super()
+    @icedHasAutocbFlag = auto
     @expression = expr if expr and not expr.unwrap().isUndefined

   children: ['expression']
@@ -727,6 +750,8 @@
     if expr and expr not instanceof Return then expr.compileToFragments o, level else super o, level

   compileNode: (o) ->
+    return @icedCompileIced o if @icedHasAutocbFlag
+
     answer = []
     # TODO: If we call expression.compile() here twice, we'll sometimes get back different results!
     answer.push @makeCode(@tab + "return#{[" " if @expression]}")
@@ -735,6 +760,13 @@
     answer.push @makeCode ";"
     return answer

+  icedCompileIced : (o) ->
+    cb = new Value new Literal iced.const.autocb
+    args = if @expression then [ @expression ] else []
+    call = new Call cb, args
+    ret = new Literal "return"
+    block = new Block [ call, ret];
+    block.compileNode o

 #### Value

@@ -742,6 +774,7 @@
 # or vanilla.
 exports.Value = class Value extends Base
   constructor: (base, props, tag) ->
+    super()
     return base if not props and base instanceof Value
     @base       = base
     @properties = props or []
@@ -749,6 +782,8 @@
     return this

   children: ['base', 'properties']
+
+  copy : -> new Value @base, @properties

   # Add a property (or *properties* ) `Access` to the list.
   add: (props) ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/point.coffee
+++ src/point.coffee
@@ -50,7 +50,8 @@
       @copy().clamp()

     clamp$: (n) ->
-      if @magnitude() >
+      if @magnitude() > n
+        norm

     ###*
     Creates a copy of this point.


Remove redundant code already found in superclass.

--- src/models.coffee
+++ src/models.coffee
@@ -131,7 +131,6 @@

     class SMCollection extends RESTFrameworkCollection
         initialize: (models, options) ->
-            @filters = {}
             @currentPage = 1
             if options?
                 @pageSize = options.pageSize || 25
@@ -145,17 +144,6 @@

         isSet: ->
             return not @isEmpty()
-
-        setFilter: (key, val) ->
-            if not val
-                if key of @filters
-                    delete @filters[key]
-            else
-                @filters[key] = val
-            return @
-
-        clearFilters: ->
-            @filters = {}

         fetchNext: (options) ->
             if @fetchState? and not @fetchState.next


payment: rearrange dom elements

payment: rearrange dom elements

--- client/Main/payment/form.coffee
+++ client/Main/payment/form.coffee
@@ -192,7 +192,7 @@
       @paypalForm
     ].forEach (view) -> view.destroy()

-    @$('hr').hide()
+    @$('.divider').detach()

     if isUpgrade
       @successMessage.updatePartial "
@@ -220,10 +220,9 @@
     ].forEach (view) -> view.destroy()

     [
-      @$('h3')
-      @$('hr')
+      @$('.divider')
       @$('.summary')
-    ].forEach (element) -> element.hide()
+    ].forEach (element) -> element.detach()

     @successMessage.updatePartial "
       We are sorry that you are having trouble upgrading.
@@ -250,7 +249,6 @@

   pistachio: ->
     """
-
You have selected

       {{> @plan}}{{> @price}}

@@ -259,7 +257,11 @@
     {{> @successMessage}}
     {{> @yearPriceMessage}}
     {{> @submitButton}}
-    #{ if @state.provider is 'koding' then '
' else '' }
+    #{
+      if @state.provider is PaymentWorkflow.provider.KODING
+      then '
OR
'
+      else ''
+    }
     {{> @paypalForm}}
     {{> @securityNote}}
     """


Execute query in a synchronous way.

Execute query in a synchronous way.

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -536,6 +536,29 @@
                     @current_results.push data
                 return false

+        # Callback for multiple queries
+        callback_multiple_query: (data) =>
+            # If we get a run time error
+            if data instanceof rethinkdb.errors.RuntimeError
+                @.$('.loading_query_img').css 'display', 'none'
+                @.results_view.render_error(@query, data)
+                return false
+
+            @current_query_index++
+            if @current_query_index is @queries.length-1
+                if @cursor?
+                    @cursor.close()
+                @cursor = eval(@queries[@current_query_index])
+                @cursor.next(@callback_query)
+            else
+                if @cursor?
+                    @cursor.close()
+                @cursor = eval(@queries[@current_query_index])
+                @cursor.next(@callback_multiple_query)
+
+            return false
+
+
         # Function triggered when the user click on 'more results'
         show_more_results: (event) =>
             try
@@ -575,14 +598,48 @@

             # Execute the query
             try
+                @queries = @separate_queries @query
                 @start_time = new Date()
-                @cursor = eval(@query)
                 @current_results = []
                 @skip_value = 0
-                @cursor.next(@callback_query)
+                if @queries.length > 1
+                    @current_query_index = 0
+                    @cursor = eval(@queries[@current_query_index])
+                    @cursor.next(@callback_multiple_query)
+                else
+                    @cursor = eval(@query)
+                    @cursor.next(@callback_query)
             catch err
                 @.$('.loading_query_img').css 'display', 'none'
                 @results_view.render_error(@query, err)
+
+        # Separate the queries so we can have an asychronous order
+        separate_queries: (query) =>
+            start = 0
+            count_dot = 0
+
+            is_string = false
+            char_used = ''
+            queries = []
+            for i in [0..query.length-1]
+                if is_string is false
+                    if (query[i] is '"' or query[i] is '\'')
+                        is_string = true
+                        char_used = query[i]
+                    else if query[i] is ';'
+                        queries.push query.slice start, i
+                        start = i+1
+                else if is_string is true
+                    if query[i] is char_used
+                        if query[i-1]? and query[i-1] is '\\'
+                            continue
+                        else
+                            is_string = false
+
+            last_query = query.slice start, query.length
+            if /^\s*$/.test(last_query) is false
+                queries.push query.slice start, query.length
+            return queries

         # Clear the input
         clear_query: =>


Fix copy/paste bug

Fix copy/paste bug

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -2353,7 +2353,7 @@
                     if database.get('name') is db_name
                         return {
                             db_found: true
-                            error
+                            error: false
                             id: database.get('id')
                             name: db_name
                         }


Update settings.cson

Update settings.cson

a little bit fixed in grammar

--- def/ru/settings.cson
+++ def/ru/settings.cson
@@ -71,7 +71,7 @@
       {
         _id: 'editor-settings-note'
         html: 'Это параметры редактирования текста. Некоторые из них
-               могут быть переведенны на исходный язык. Проверяйте язык
+               могут быть переведены на исходный язык. Проверяйте язык
                настроек каждого расширения во вкладке
                Расширения.'
       }
@@ -437,7 +437,7 @@
   install: {
     notes: {
       text1: "Расширения публикуются на "
-      text2: " и они установленны в "
+      text2: " и они установлены в "
       text3: ""   # optional
       _template: "${text1}atom.io${text2} /path/to/pkg ${text3}"
     }


Added punctuation to the alias too.

Added punctuation to the alias too.

Before an alias called "megatron" couldn't be mentioned with a ":" or "," (e.g.: "MEGATRON: the rules") just without it. This change makes the alias work just like the "Hubot".

--- src/robot.coffee
+++ src/robot.coffee
@@ -53,7 +53,7 @@
     pattern = re.join("/") # combine the pattern back again
     if @alias
       alias = @alias.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&") # escape alias for regexp
-      newRegex = new RegExp("^(?:#{alias}|#{@name}[:,]?)\\s*(?:#{pattern})", modifiers)
+      newRegex = new RegExp("^(?:#{alias}[:,]?|#{@name}[:,]?)\\s*(?:#{pattern})", modifiers)
     else
       newRegex = new RegExp("^#{@name}[:,]?\\s*(?:#{pattern})", modifiers)


Fix r.ISO8601 + select_suggestion (with the mouse)

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -1266,7 +1266,7 @@
         regex:
             anonymous:/^(\s)*function\(([a-zA-Z0-9,\s]*)\)(\s)*{/
             loop:/^(\s)*(for|while)(\s)*\(([^\)]*)\)(\s)*{/
-            method: /^(\s)*([a-zA-Z]*)\(/ # forEach( merge( filter(
+            method: /^(\s)*([a-zA-Z0-9]*)\(/ # forEach( merge( filter(
             row: /^(\s)*row\(/
             method_var: /^(\s)*(\d*[a-zA-Z][a-zA-Z0-9]*)\./ # r. r.row. (r.count will be caught later)
             return : /^(\s)*return(\s)*/
@@ -2184,9 +2184,10 @@
             # Give back focus to code mirror
             @hide_suggestion()

-            @handle_keypress() # That's going to describe the function the user just selected
-
-            @codemirror.focus() # Useful if the user used the mouse to select a suggestion
+            setTimeout =>
+                @handle_keypress() # That's going to describe the function the user just selected
+                @codemirror.focus() # Useful if the user used the mouse to select a suggestion
+            , 0 # # Useful if the user used the mouse to select a suggestion

         # Highlight a suggestion in case of a mouseover


expose the router so we can use http verbs

--- src/robot.coffee
+++ src/robot.coffee
@@ -154,6 +154,8 @@
     @connect = Connect()
     if user and pass
       @connect.use Connect.basicAuth(user, path)
+    @connect.use Connect.router (app) =>
+      @router = app

     @connect.listen process.env.PORT


no need for compose here

no need for compose here

--- src/object.coffee
+++ src/object.coffee
@@ -1,4 +1,4 @@
-{compose, curry, negate} = require "fairmont-core"
+{curry, negate} = require "fairmont-core"
 {isObject} = require "./type"
 {deepEqual} = require "./util"


Fix parse help to work synchronously

Fix parse help to work synchronously

--- src/robot.coffee
+++ src/robot.coffee
@@ -347,37 +347,36 @@
     scriptName = Path.basename(path).replace /\.(coffee|js)$/, ''
     scriptDocumentation = {}

-    Fs.readFile path, 'utf-8', (err, body) =>
-      throw err if err?
-
-      currentSection = null
-      for line in body.split "\n"
-        break unless line[0] is '#' or line.substr(0, 2) is '//'
-
-        cleanedLine = line.replace(/^(#|\/\/)\s?/, "").trim()
-
-        continue if cleanedLine.length is 0
-        continue if cleanedLine.toLowerCase() is 'none'
-
-        nextSection = cleanedLine.toLowerCase().replace(':', '')
-        if nextSection in HUBOT_DOCUMENTATION_SECTIONS
-          currentSection = nextSection
-          scriptDocumentation[currentSection] = []
-        else
-          if currentSection
-            scriptDocumentation[currentSection].push cleanedLine.trim()
-            if currentSection is 'commands'
-              @commands.push cleanedLine.trim()
-
-      if currentSection is null
-        @logger.info "#{path} is using deprecated documentation syntax"
-        scriptDocumentation.commands = []
-        for line in body.split("\n")
-          break    if not (line[0] is '#' or line.substr(0, 2) is '//')
-          continue if not line.match('-')
-          cleanedLine = line[2..line.length].replace(/^hubot/i, @name).trim()
-          scriptDocumentation.commands.push cleanedLine
-          @commands.push cleanedLine
+    body = Fs.readFileSync path, 'utf-8'
+
+    currentSection = null
+    for line in body.split "\n"
+      break unless line[0] is '#' or line.substr(0, 2) is '//'
+
+      cleanedLine = line.replace(/^(#|\/\/)\s?/, "").trim()
+
+      continue if cleanedLine.length is 0
+      continue if cleanedLine.toLowerCase() is 'none'
+
+      nextSection = cleanedLine.toLowerCase().replace(':', '')
+      if nextSection in HUBOT_DOCUMENTATION_SECTIONS
+        currentSection = nextSection
+        scriptDocumentation[currentSection] = []
+      else
+        if currentSection
+          scriptDocumentation[currentSection].push cleanedLine.trim()
+          if currentSection is 'commands'
+            @commands.push cleanedLine.trim()
+
+    if currentSection is null
+      @logger.info "#{path} is using deprecated documentation syntax"
+      scriptDocumentation.commands = []
+      for line in body.split("\n")
+        break    if not (line[0] is '#' or line.substr(0, 2) is '//')
+        continue if not line.match('-')
+        cleanedLine = line[2..line.length].replace(/^hubot/i, @name).trim()
+        scriptDocumentation.commands.push cleanedLine
+        @commands.push cleanedLine

   # Public: A helper send function which delegates to the adapter's send
   # function.


code style fixed

code style fixed

--- src/robot.coffee
+++ src/robot.coffee
@@ -224,7 +224,7 @@
   # Setup the Express server's defaults.
   #
   # Returns nothing.
-  setupExpress: ()->
+  setupExpress: ->
     user    = process.env.EXPRESS_USER
     pass    = process.env.EXPRESS_PASSWORD
     stat    = process.env.EXPRESS_STATIC
@@ -252,7 +252,7 @@
   # Setup an empty router object
   #
   # returns nothing
-  setupNullRouter: ()->
+  setupNullRouter: ->
     msg = "Some of your scipts reports: please run hubot without --disable-httpd"
     @router =
       get: ()=> @logger.warning msg


Reuse Event.listenTo/listenToOnce/stopListening in Module class

--- src/spine.coffee
+++ src/spine.coffee
@@ -445,39 +445,9 @@
     args.splice(1, 0, this)
     @constructor.trigger(args...)

-  listenTo: (obj, events, callback) ->
-    obj.bind events, callback
-    @listeningTo or= []
-    @listeningTo.push(obj)
-
-  listenToOnce: (obj, events, callback) ->
-    listeningToOnce = @listeningToOnce or= []
-    listeningToOnce.push obj
-    obj.bind events, handler = =>
-      idx = listeningToOnce.indexOf(obj)
-      listeningToOnce.splice(idx, 1) unless idx is -1
-      obj.unbind(events, handler)
-      callback.apply(obj, arguments)
-
-  stopListening: (obj, events, callback) ->
-    if arguments.length is 0
-      retain = []
-      for listeningTo in [@listeningTo, @listeningToOnce]
-        continue unless listeningTo
-        for obj in @listeningTo when not (obj in retain)
-          obj.unbind()
-          retain.push(obj)
-      @listeningTo = undefined
-      @listeningToOnce = undefined
-      return
-
-    if obj
-      obj.unbind() unless events
-      obj.unbind(events, callback) if events
-      for listeningTo in [@listeningTo, @listeningToOnce]
-        continue unless listeningTo
-        idx = listeningTo.indexOf(obj)
-        listeningTo.splice(idx, 1) unless idx is -1
+  listenTo: -> Events.listenTo.apply @, arguments
+  listenToOnce: -> Events.listenToOnce.apply @, arguments
+  stopListening: -> Events.stopListening.apply @, arguments

   unbind: (events, callback) ->
     if arguments.length is 0


update

update

--- app/assets/javascripts/common.js.coffee
+++ app/assets/javascripts/common.js.coffee
@@ -59,7 +59,7 @@
     timeline.reset_timeline()

 frameTransparent = () ->
-  $('iframe').each =>
+  $('iframe').each ->
     url = $(this).attr("src")
     $(this).attr("src",url+"?wmode=transparent")

@@ -99,6 +99,7 @@
 # responsiveWindow init
 $ ->
   responsiveWindow()
+  frameTransparent()

   $(window).resize ->
     responsiveWindow()


Auto submit header search query on autocomplete selection.

--- app/assets/javascripts/search.js.coffee
+++ app/assets/javascripts/search.js.coffee
@@ -1,4 +1,5 @@
 # Place all the behaviors and hooks related to the matching controller here.
+#
 # All this logic will automatically be available in application.js.
 # You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/

@@ -13,7 +14,7 @@
   });

   $("input.typeahead").on "typeahead:selected", (event) ->
-    $("input.typeahead").parent().parent().submit();
+    $("input.typeahead").closest('form').submit();
   $("input.typeahead_home").on "typeahead:selected", (event) ->
     $("input.typeahead_home").parent().parent().submit();


Properly convert between encodings.

Properly convert between encodings.

--- src/stews.coffee
+++ src/stews.coffee
@@ -9,10 +9,12 @@
 module.exports = (robot) ->
   robot.respond /(hos frank|franks)/i, (msg) ->
     robot.http("http://www.aptit.se/0909/index/products/productsved.asp?x=1&SupplierID=105")
+      .encoding("binary")
       .get() (err, res, body) ->
         if res.statusCode is 200
+          body = iconvlite.decode(body, "iso88591")
           $ = cheerio.load(body)
-          stews = (iconvlite.decode($(stew).text(), 'utf-8') for stew in $("#category_895 .txtb").toArray())
+          stews = ($(stew).text() for stew in $("#category_895 .txtb").toArray())

           response = ""


Add an unbind callback to the each-* binder that unbinds all iterated views. [Closes #153]

--- src/rivets.coffee
+++ src/rivets.coffee
@@ -345,6 +345,8 @@
     block: true
     bind: (el, collection) ->
       el.removeAttribute ['data', Rivets.config.prefix, @type].join('-').replace '--', '-'
+    unbind: (el, collection) ->
+      view.unbind() for view in @iterated if @iterated?
     routine: (el, collection) ->
       if @iterated?
         for view in @iterated


fixed some subtle bugs

fixed some subtle bugs

--- src/sentai.coffee
+++ src/sentai.coffee
@@ -98,6 +98,7 @@
     for name, extension of extensions
       component.prototype[name] = extension

+  component.prototype._entity = null
   component.prototype._sync = null
   component.prototype._listensTo = null
   component.prototype._observes = null
@@ -144,8 +145,10 @@
       for component in components
         # check for special name
         name = component.name
+        component.prototype._entity = @
         componentInstance = @_components[name] = new component(@, options[name] || options)
         componentInstance._entity = @
+        component.prototype._entity = null
         componentInstances.push(componentInstance)
         events = component.prototype._listensTo
         if events?
@@ -179,7 +182,7 @@
               @[v] = componentInstance['__' + v]

   for v, getSet of getSets
-    config = (__v) ->
+    config = (__v, getSet) ->
       return {
         get: ()->
           return @[__v]
@@ -193,7 +196,7 @@
                 args.push(@[arg])
               getSetConfig.cb.apply(@_components[getSetConfig.ctx], args)
       }
-    Object.defineProperty(NewClass.prototype, v, config('__' + v))
+    Object.defineProperty(NewClass.prototype, v, config('__' + v, getSet))

   return NewClass


Use JSON rgb object instead of deprecated halo string format

--- src/server.coffee
+++ src/server.coffee
@@ -72,22 +72,15 @@
     ), 15

   #
-  # convert halo rgba string to a UART instruction
+  # convert array of rgb objects into a UART instruction for arduino to process and pipe to LEDs
   # @example
-  #   _data_to_instruction({ color: '000,110,255,000\n255,000,000,000' })
-  #   # => '345,5,1,255,000,000;2,255,000,000,3,000,000,000,4,000,000,000,5,000,000,000;'
+  #   _data_to_instruction(color: [{ r: 100, g: 50, b: 0 }, { r: 100, g: 50, b: 0 }, ...)
+  #   # => '345,5,1,100,50,0;2,100,50,0,3,000,000,000,4,000,000,000,5,000,000,000;'
   #
   _data_to_instruction: (data) ->
-    # break colors string into array
-    colors = data.color.split '\n'
-    colors.pop()
-
-    # remove `v` value from each color
-    colors = colors.map (c) -> c.slice(0,-4)
-
     # build our TTY instruction
     instruction = '345,5,'
-    instruction += "#{i+1},#{color};" for color, i in colors
+    instruction += "#{i+1},#{color.r},#{color.g},#{color.b};" for color, i in data.color

     # padding with black if necessary
     instruction_count = (instruction.match(/;/g)||[]).length


(Fix) Pseudonym timeout was incorrectly implemented.

--- src/users.coffee
+++ src/users.coffee
@@ -11,7 +11,7 @@
   config.lockTime = config.lockTime or 15
   clearPendingPseudonyms = ->
     root.DB.PseudonymList = _.reject root.DB.PseudonymList, (p) ->
-      p.locked and moment().add(config.lockTime, "minutes").isAfter p.locked
+      p.locked and moment().subtract(config.lockTime, "minutes").isAfter p.locked

   exists: (id) ->
     new Promise (resolve, reject) ->
@@ -34,7 +34,7 @@
   setPseudonym: (id, newPseudonym) ->
     new Promise (resolve, reject) ->
       clearPendingPseudonyms()
-      pseudonymUser = _.select root.DB.PseudonymList, (u) -> u.pseudonym == newPseudonym or u.user == id
+      pseudonymUser = _.select root.DB.PseudonymList, (u) -> u.pseudonym == newPseudonym and u.user != id
       if pseudonymUser.length > 0
         reject "Pseudonym #{newPseudonym} already locked"
         return


More utils

More utils

--- src/utils.coffee
+++ src/utils.coffee
@@ -155,6 +155,9 @@
 	else
 		0

+exports.pointInRect = (point, rect) ->
+	alert "Not implemented, you lazy man"
+
 exports.uuid = ->

 	chars = '0123456789abcdefghijklmnopqrstuvwxyz'.split('')
@@ -169,17 +172,12 @@

 	output.join('')

+exports.isWebKit = ->
+	window.WebKitCSSMatrix isnt null

+exports.isTouch = ->
+	window.ontouchstart is null

-exports.isWebKit = ->
-
-	isChrome = /Chrome/.test(navigator.userAgent) and \
-		/Google Inc/.test(navigator.vendor)
-	isSafari = /Safari/.test(navigator.userAgent) and \
-		 /Apple Computer/.test(navigator.vendor)
-
-	return true in [isChrome, isSafari]
-
-
-
-
+exports.isMobile = ->
+	(/iphone|ipod|android|ie|blackberry|fennec/).test \
+		navigator.userAgent.toLowerCase()


fix: utils.throw bug

fix: utils.throw bug

--- src/utils.coffee
+++ src/utils.coffee
@@ -335,5 +335,5 @@
 	 * ```
 	###
 	throw: (err) ->
-		setTimeout -> err
+		setTimeout -> throw err
 		return


add exception when merging ol/ul

add exception when merging ol/ul

--- src/utils.coffee
+++ src/utils.coffee
@@ -153,6 +153,8 @@
     return node1 if !node2?
     this.moveChildren(node1, node2)
     node2.parentNode.removeChild(node2)
+    if (node1.tagName == 'OL' || node1.tagName == 'UL') && node1.childNodes.length == 2
+      TandemUtils.mergeNodes(node1.firstChild, node1.lastChild)
     return node1

   moveChildren: (newParent, oldParent) ->


JSDoc for mixed-in interfaces

JSDoc for mixed-in interfaces

--- src/views.coffee
+++ src/views.coffee
@@ -45,6 +45,7 @@
 * Table Container
 * @class
 * @alias Ember.Table.TableContainer
+* @mixes Ember.StyleBindingsMixin
 ###
 Ember.Table.TableContainer = Ember.View.extend Ember.StyleBindingsMixin,
   classNames:     ['table-container']
@@ -54,6 +55,7 @@
 * Table Block
 * @class
 * @alias Ember.Table.TableBlock
+* @mixes Ember.StyleBindingsMixin
 * @todo This should be a mixin
 ###
 Ember.Table.TableBlock = Ember.CollectionView.extend Ember.StyleBindingsMixin,
@@ -145,6 +147,7 @@
 * Table Cell
 * @class
 * @alias Ember.Table.TableCell
+* @mixes Ember.StyleBindingsMixin
 ###
 Ember.Table.TableCell = Ember.View.extend Ember.StyleBindingsMixin,
   defaultTemplate: Ember.Handlebars.compile(
@@ -197,6 +200,7 @@
 * Header Row
 * @class
 * @alias Ember.Table.HeaderRow
+* @mixes Ember.StyleBindingsMixin
 ###
 Ember.Table.HeaderRow = Ember.View.extend Ember.StyleBindingsMixin,
   templateName:   'header-row'
@@ -250,6 +254,7 @@
 * Header Cell
 * @class
 * @alias Ember.Table.HeaderCell
+* @mixes Ember.StyleBindingsMixin
 ###
 Ember.Table.HeaderCell = Ember.View.extend Ember.StyleBindingsMixin,
   templateName:   'header-cell'
@@ -301,6 +306,8 @@
 * @class
 * @alias Ember.Table.HeaderTableContainer
 * @augments Ember.Table.TableContainer
+* @mixes Ember.MouseWheelHandlerMixin
+* @mixes Ember.TouchMoveHandlerMixin
 ###
 Ember.Table.HeaderTableContainer =
 Ember.Table.TableContainer.extend Ember.MouseWheelHandlerMixin,
@@ -343,6 +350,9 @@
 * Body Table Container
 * @class
 * @alias Ember.Table.BodyTableContainer
+* @mixes Ember.MouseWheelHandlerMixin
+* @mixes Ember.TouchMoveHandlerMixin
+* @mixes Ember.ScrollHandlerMixin
 ###
 Ember.Table.BodyTableContainer =
 Ember.Table.TableContainer.extend Ember.MouseWheelHandlerMixin,
@@ -407,6 +417,8 @@
 * Footer Table Container
 * @class
 * @alias Ember.Table.FooterTableContainer
+* @mixes Ember.MouseWheelHandlerMixin
+* @mixes Ember.TouchMoveHandlerMixin
 ###
 Ember.Table.FooterTableContainer =
 Ember.Table.TableContainer.extend Ember.MouseWheelHandlerMixin,
@@ -438,6 +450,8 @@
 * Scroll Container
 * @class
 * @alias Ember.Table.ScrollContainer
+* @mixes Ember.StyleBindingsMixin
+* @mixes Ember.ScrollHandlerMixin
 ###
 Ember.Table.ScrollContainer =
 Ember.View.extend Ember.StyleBindingsMixin, Ember.ScrollHandlerMixin,
@@ -474,6 +488,7 @@
 * ScrollPanel
 * @class
 * @alias Ember.Table.ScrollPanel
+* @mixes Ember.StyleBindingsMixin
 ###
 Ember.Table.ScrollPanel = Ember.View.extend Ember.StyleBindingsMixin,
   classNames:     ['scroll-panel']


remove logger, have custom server require "loggy" on its own

--- src/watch.coffee
+++ src/watch.coffee
@@ -70,7 +70,7 @@
       throw new Error 'Brunch server file needs to have startServer function'
     if serverOpts.config
       opts = {port, path: publicPath}
-      server.startServer helpers.extend(opts, serverOpts.config), callback, logger
+      server.startServer helpers.extend(opts, serverOpts.config), callback
     else
       server.startServer port, publicPath, log
   else


Pass additional server options to pushserve.

--- src/watch.coffee
+++ src/watch.coffee
@@ -52,6 +52,7 @@
   params

 startServer = (config, callback = (->)) ->
+  serverOpts = config.server or {}
   port = parseInt config.server.port, 10
   publicPath = config.paths.public
   log = ->
@@ -66,7 +67,8 @@
       throw new Error 'Brunch server file needs to have startServer function'
     server.startServer port, publicPath, log
   else
-    pushserve {port, path: publicPath, base: config.server.base, noLog: true}, log
+    opts = noLog: yes, path: publicPath
+    pushserve helpers.extend(opts, serverOpts), log

 # Filter paths that exist and watch them with `chokidar` package.
 #


Removed "Experimental" from Lua...

Removed "Experimental" from Lua...

... and added to CoffeeScript

--- app/views/ladder/LadderPlayModal.coffee
+++ app/views/ladder/LadderPlayModal.coffee
@@ -91,9 +91,9 @@
     ctx.languages = [
       {id: 'python', name: 'Python'}
       {id: 'javascript', name: 'JavaScript'}
-      {id: 'coffeescript', name: 'CoffeeScript'}
+      {id: 'coffeescript', name: 'CoffeeScript (Experimental)'}
       {id: 'clojure', name: 'Clojure (Experimental)'}
-      {id: 'lua', name: 'Lua (Experimental)'}
+      {id: 'lua', name: 'Lua'}
       {id: 'io', name: 'Io (Experimental)'}
     ]
     teamsList = teamDataFromLevel @level


hide subscribe modal for anonymous taiwan users (#6567)

Co-authored-by: smallst_wsl <6c42689ff77371401bf43b7d56b93ac1ca3e92d3@163.com>

--- app/views/play/CampaignView.coco.coffee
+++ app/views/play/CampaignView.coco.coffee
@@ -1534,7 +1534,7 @@
       return not (isIOS or me.freeOnly() or isStudentOrTeacher or !me.canBuyGems() or (application.getHocCampaign() and me.isAnonymous()))

     if what in ['premium']
-      return not (me.isPremium() or isIOS or me.freeOnly() or isStudentOrTeacher or (application.getHocCampaign() and me.isAnonymous()) or paymentUtils.hasTemporaryPremiumAccess())
+      return not (me.isPremium() or isIOS or me.freeOnly() or isStudentOrTeacher or (application.getHocCampaign() and me.isAnonymous()) or paymentUtils.hasTemporaryPremiumAccess() or (me.isAnonymous() and me.get('country') is 'taiwan')) # temporary hide subscription for anonymous taiwan users

     if what is 'anonymous-classroom-signup'
       return me.isAnonymous() and me.level() < 8 and me.promptForClassroomSignup() and not @editorMode


Update the message with string interpolation

--- src/ambush.coffee
+++ src/ambush.coffee
@@ -37,5 +37,5 @@
     return unless robot.brain.data.ambushes?
     if (ambushes = robot.brain.data.ambushes[msg.message.user.name])
       for ambush in ambushes
-        msg.send msg.message.user.name + ": while you were out, " + ambush[0] + " said: " + ambush[1]
+        msg.send "#{msg.message.user.name}: while you were out, #{ambush[0]} said: #{ambush[1]}"
       delete robot.brain.data.ambushes[msg.message.user.name]


fix(vm.*): add missing import

fix(vm.*): add missing import

--- src/api/vm.coffee
+++ src/api/vm.coffee
@@ -13,6 +13,7 @@

 {
   GenericError,
+  InvalidParameters,
   Unauthorized
 } = require('../api-errors')
 {


Fix syntax.

Fix syntax.

--- src/api/vm.coffee
+++ src/api/vm.coffee
@@ -785,7 +785,7 @@
   return {
     $getFrom: $wait @registerHttpRequest(handleExport, {
       stream,
-      $wait stream.response
+      response: $wait stream.response
     })
   }


revoked changes in help

revoked changes in help

--- src/badges.coffee
+++ src/badges.coffee
@@ -5,12 +5,12 @@
 #   None
 #
 # Commands:
-#   hubot I have the <badge> badge - add/remove badges (Ingress)
-#   hubot I don't have any badges - remove your badges completely (Ingress)
-#   hubot what badges do I have? - show off your Ingress badges (Ingress)
-#   hubot what badges does <person> have? - check another agent's badges (Ingress)
-#   hubot list badges - list the badges available (Ingress)
-#   hubot display badges - displays all the badge titles and the badges (Ingress)
+#   hubot I have the <badge> badge - add/remove badges (say don't to remove)
+#   hubot I don't have any badges - remove your badges completely
+#   hubot what badges do I have? - show off your Ingress badgesyou worked hard for them!
+#   hubot what badges does <person> have? - check another agent's badges
+#   hubot list badges - list the badges available
+#   hubot display badges - displays all the badge titles and the badges
 #
 # Author:
 #   therealklanni


JSAlertView update

JSAlertView update

--- JSKit/03_JSAlertView.coffee
+++ JSKit/03_JSAlertView.coffee
@@ -67,6 +67,8 @@
 			modal: true
 			closeOnEscape: true
 			close:=>
+				if (@delegate? && typeof @delegate.clickedButtonAtIndex == "function")
+					@delegate.clickedButtonAtIndex(0, @_self)
 				$(@_viewSelector+"_form").dialog("close")
 				@_self.removeFromSuperview()
 		alerthash["buttons"] = buttonhash


Cache decoy width and trigger events when hiding or displaying

--- components/carousel/view.coffee
+++ components/carousel/view.coffee
@@ -93,14 +93,21 @@
     # Must reset these or the throttled resize will use stale values.
     @stopPositions = []
     windowWidth = @$window.width()
-    if @$preDecoys.width() < windowWidth
+
+    # The decoys width isn't going to change, so cache it, which helps
+    # if we want to externally show and hide the carousel
+    @preDecoysWidth ?= @$preDecoys.width()
+
+    if @preDecoysWidth < windowWidth
       @noDecoys = true
       @$decoys.hide()
       @prefixWidth = 0
+      @trigger 'width:insufficient'
     else
       @noDecoys = false
       @$decoys.show()
       @prefixWidth = @$preDecoys.width()
+      @trigger 'width:sufficient'

     totalLefts = @prefixWidth
     @$figures.each (index, figure) =>


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/camera.coffee
+++ src/camera.coffee
@@ -35,7 +35,7 @@
       width: 2 * deadzone.x
       height: 2 * deadzone.y

-    if objectCenter.x >
+    if objectCenter.x > cen
     I.scroll = Point(
       I.scroll.x.clamp(centerRect.left, centerRect.right)
       I.scroll.y.clamp(centerRect.top, centerRect.bottom)


Bug fix: View scrollbar when console log overflows

--- lib/Components/ConsolePane.coffee
+++ lib/Components/ConsolePane.coffee
@@ -91,7 +91,8 @@
     }, [
       h('div.debugger-panel-heading', {}, ['stdout/stderr'])
       h('div.panel-body.padded', style: {
-        flex: 'auto'
+        flex: '1'
+        overflow: 'auto'
       }, state.lines.map(tokenizeLine))
       h('div.debugger-editor', style: {
         height: '33px'


minor fix

minor fix

--- library/exposure/inventory.coffee
+++ library/exposure/inventory.coffee
@@ -84,9 +84,9 @@
         malfReg = "the routing registry seems is broken"
         noKernRout = "failed to obtain the kernel router"
         message = "Building entire API inventory in %s"
-        logger.debug message.toString().green, identify
         pub = (sv) => not sv.constructor.HIDE_INVENTORY
         predicate = (sv) => sv.objectOf(Asc) and pub(sv)
+        logger.debug message.toString().magenta, identify
         assert router = (try @kernel.router), noKernRout
         assert registry = (try router.registry), malfReg
         assert apis = _.filter(registry, predicate) or []


Fix the indicies overriding issues

Fix the indicies overriding issues

--- library/membrane/preflight.coffee
+++ library/membrane/preflight.coffee
@@ -103,12 +103,12 @@
         return next f context if f = bowerings.cached
         options = directory: bowerings.directory
         esc = (p) -> new RegExp RegExp.escape "#{p}"
-        match = (k) -> (b) -> b.target.match esc k
+        match = (k) -> (b) -> esc(k).test b.target
         sorter = (v, k) -> _.findIndex bowerings, match(k)
         list(paths: yes, options).on "end", (paths) ->
             bowerings.cached = (context) ->
-                paths = _.sortBy paths, sorter
-                files = _.flatten _.values(paths)
+                sorted = _.sortBy paths, sorter
+                files = _.flatten _.values sorted
                 for file in files then do (file) ->
                     ext = (e) -> path.extname(file) is e
                     context.scripts.push file if ext ".js"


Added timer in downloadInBrowser() to wait until file URL is available.

--- src/common.coffee
+++ src/common.coffee
@@ -104,8 +104,18 @@
           Blobs.fromString(data, type: file.type())

       downloadInBrowser: (fileId) ->
-        file = collection.findOne(fileId)
-        Window.downloadFile(file.url())
+        file = collection.findOne(_id: fileId)
+        unless file then return Logger.error('File not found: ' + fileId)
+        Logger.info 'Downloading file', fileId, file
+        # Wait for the file to be synced to the client and the URL to be propagated.
+        handle = null
+        handler = ->
+          file = collection.findOne(_id: fileId)
+          if file?.url()
+            clearInterval(handle)
+            Window.downloadFile(file.url())
+        handle = setInterval handler, 1000
+        handler()

 if Meteor.isServer
   createTempStore = _.once (args) ->


FIX: error in build/compile

FIX: error in build/compile

--- build/publish.coffee
+++ build/publish.coffee
@@ -2,7 +2,7 @@
 gutil = require 'gulp-util'
 fs = require 'fs'
 request = require 'request'
-# changelogs = require './helpers/changelogs'
+changelogs = require './helpers/changelogs'
 handleError = require './helpers/error'
 compile = require './compile'
 test = require './test'


Be more tolerant.

Be more tolerant.

--- debug.coffee
+++ debug.coffee
@@ -16,6 +16,8 @@
     @endComponent component

   @dumpComponentSubtree: (rootComponent, _markComponent=(->)) ->
+    return unless rootComponent
+
     marked = _markComponent rootComponent

     if marked
@@ -40,4 +42,6 @@
     component

   @dumpComponentTree: (component) ->
+    return unless component
+
     @dumpComponentSubtree @componentRoot(component), (c) -> c is component


add logging

add logging

--- libraries/metrics/metrics.coffee
+++ libraries/metrics/metrics.coffee
@@ -54,6 +54,7 @@
 	set : (key, value, sampleRate = 1)->

 	inc : (key, sampleRate = 1, opts = {})->
+		console.log("doing inc", key)
 		key = Metrics.buildPromKey(key)
 		if !promMetrics[key]?
 			promMetrics[key] = new prom.Counter({


Make register public so we can support other servers e.g. hapi

--- libraries/metrics/metrics.coffee
+++ libraries/metrics/metrics.coffee
@@ -1,5 +1,5 @@
 prom = require('prom-client')
-Register = require('prom-client').register
+
 collectDefaultMetrics = prom.collectDefaultMetrics

 appname = "unknown"
@@ -15,6 +15,7 @@
 require "./uv_threadpool_size"

 module.exports = Metrics =
+	register: require('prom-client').register
 	initialize: (_name) ->
 		appname = _name
 		collectDefaultMetrics({ timeout: 5000, prefix: Metrics.buildPromKey()})
@@ -60,8 +61,8 @@

 	injectMetricsRoute: (app) ->
 		app.get('/metrics', (req, res) ->
-			res.set('Content-Type', Register.contentType)
-			res.end(Register.metrics())
+			res.set('Content-Type', register.contentType)
+			res.end(register.metrics())
 		)

 	buildPromKey: (key = "")->


Chat: change label of purpose field

Chat: change label of purpose field

--- client/Social/Activity/views/privatemessage/modal.coffee
+++ client/Social/Activity/views/privatemessage/modal.coffee
@@ -30,7 +30,7 @@
             recipient      :
               itemClass    : KDView
             purpose        :
-              placeholder  : 'What\'s your purpose?'
+              placeholder  : 'Purpose? (optional)'
               name         : 'purpose'
             body           :
               label        : ''


StackTemplateListController: Show no item found widget after last item is removed

--- client/app/lib/stacks/stacktemplatelistcontroller.coffee
+++ client/app/lib/stacks/stacktemplatelistcontroller.coffee
@@ -53,11 +53,12 @@

   bindEvents: ->

+    super
+
     listView = @getListView()

     listView.on 'ItemAction', ({ action, item, options }) =>
       switch action
-        when 'RemoveItem'            then @removeItem     item
         when 'ShowItem'              then @showItem       item
         when 'EditItem'              then @editItem       item
         when 'ItemSelectedAsDefault' then @applyToTeam    item
@@ -215,9 +216,12 @@
       callback    : ({ status, modal }) ->
         return  unless status
         template.delete (err) ->
-          listView.removeItem item
+
+          listView.emit 'ItemAction', { action : 'ItemRemoved', item }
+
           if template.accessLevel is 'group'
             currentGroup.sendNotification 'GroupStackTemplateRemoved', template._id
+
           modal.destroy()
           Tracker.track Tracker.STACKS_DELETE_TEMPLATE