Avoid saving windows state when quitting on windows/linux

--- src/browser/atom-application.coffee
+++ src/browser/atom-application.coffee
@@ -99,7 +99,11 @@
   # Public: Removes the {AtomWindow} from the global window list.
   removeWindow: (window) ->
     @windows.splice @windows.indexOf(window), 1
-    @applicationMenu?.enableWindowSpecificItems(false) if @windows.length is 0
+    if @windows.length is 0
+      @applicationMenu?.enableWindowSpecificItems(false)
+      if process.platform in ['win32', 'linux']
+        app.quit()
+        return
     @saveState()

   # Public: Adds the {AtomWindow} to the global window list.
@@ -201,9 +205,6 @@
     @openPathOnEvent('application:open-your-stylesheet', 'atom://.atom/stylesheet')
     @openPathOnEvent('application:open-license', path.join(process.resourcesPath, 'LICENSE.md'))

-    app.on 'window-all-closed', ->
-      app.quit() if process.platform in ['win32', 'linux']
-
     app.on 'will-quit', =>
       @killAllProcesses()
       @deleteSocketFile()


Add showNotyNetworkError to app/core/errors

--- app/core/errors.coffee
+++ app/core/errors.coffee
@@ -41,6 +41,15 @@
   )
   showErrorModal(html)

+module.exports.showNotyNetworkError = (jqxhr) ->
+  noty({
+    text: jqxhr.responseText or 'Unknown error'
+    layout: 'topCenter'
+    type: 'error'
+    killer: false,
+    dismissQueue: true
+  })
+
 showErrorModal = (html) ->
   # TODO: make a views/modal/error_modal view for this to use so the template can reuse templates/core/modal-base?
   $('#modal-wrapper').html(html)


Prefer the contact button, otherwise use the inquiry button

--- apps/artwork/client/view.coffee
+++ apps/artwork/client/view.coffee
@@ -71,7 +71,7 @@
     @artwork.fetch()

   setupZigZag: ->
-    if ($inquiryButton = @$('.artwork-inquiry a')).length
+    if ($inquiryButton = @$('.artwork-contact-button, .artwork-inquiry-button').first()).length
       new ZigZagBanner
         persist : true
         name    : 'inquiry'


Removed redundant code, as we already unset the event handler

--- app/js/loader.coffee
+++ app/js/loader.coffee
@@ -40,13 +40,10 @@
 else
   coreScriptTag.src = FactlinkConfig.lib + FactlinkConfig.srcPath

-scriptLoaded = false
 coreScriptTag.onload = coreScriptTag.onreadystatechange = ->
-  return false if scriptLoaded
   return false if coreScriptTag.readyState? not in ['complete', 'loaded']

   coreScriptTag.onload = coreScriptTag.onreadystatechange = null
-  scriptLoaded = true

   window.FACTLINK = iframe.contentWindow.Factlink
   jQuery?(window).trigger 'factlink.libraryLoaded'


board: fix th IE10 JavaScript legacy fix

--- app/assets/javascripts/chat.coffee
+++ app/assets/javascripts/chat.coffee
@@ -112,9 +112,9 @@
     date = /\d{4}-\d{2}-\d{2}/.exec(norlogeDatetime)
     time = /\d{2}:\d{2}:\d{2}/.exec(norlogeDatetime)
     index = @board.find(".board-left time[data-clock-date=\"" + date + "\"][data-clock-time=\"" + time + "\"]").length + 1
-    $(x).attr("data-clockDate", date)
-    $(x).attr("data-clockTime", time)
-    $(x).attr("data-clockIndex", index)
+    $(x).attr("data-clock-date", date)
+    $(x).attr("data-clock-time", time)
+    $(x).attr("data-clock-index", index)

   left_highlitizer: (event) =>
     time = $(event.target).data("clockTime")


add script for detecting IE

add script for detecting IE

--- app/assets/javascripts/init.coffee
+++ app/assets/javascripts/init.coffee
@@ -43,6 +43,17 @@
       else
         window.open(local, '_blank')

+  # We need to call this because of Flexbox
+  # Edge is fine, but all versions of IE are broken, and we should tell peeps
+  msieversion = ->
+    ua = window.navigator.userAgent
+    msie = ua.indexOf('MSIE ')
+    if msie > 0 or ! !navigator.userAgent.match(/Trident.*rv\:11\./)
+      alert parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)))
+    else
+      alert 'otherbrowser'
+    false
+

 $(document).ready ->
   window.BikeIndexInit = new window.BikeIndex


bugfix

bugfix

--- app/assets/javascripts/main.coffee
+++ app/assets/javascripts/main.coffee
@@ -32,6 +32,8 @@
                 if notExists
                     # TODO: ick. Use views for this.
                     checked = ""
+                    cl_checked = ""
+                    mcx_checked = ""
                     if i.pin_enabled == "true"
                         checked = "checked"
                     if i.contactless_enabled == "true"
@@ -162,6 +164,8 @@

                 # TODO: ick. Use views for this.
                 checked = ""
+                cl_checked = ""
+                mcx_checked = ""
                 if data.pin_enabled == "true"
                     checked = "checked"
                 if data.contactless_enabled == "true"
@@ -180,7 +184,6 @@
                     pinColor = "FE7569";
                 else
                     pinColor = "00FF00"
-

                 pinImage = new google.maps.MarkerImage("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=%E2%80%A2|" + pinColor,
                     new google.maps.Size(21, 34),


Listen to Turbolinks 5 `turbolinks:load` event.

Turbolinks 5 uses `turbolinks:load` instead of `page:change` for page re-renders.

--- app/assets/javascripts/peek.coffee
+++ app/assets/javascripts/peek.coffee
@@ -62,7 +62,7 @@
       $(this).trigger 'peek:update'

   # Also listen to turbolinks page change event
-  $(document).on 'page:change', ->
+  $(document).on 'page:change turbolinks:load', ->
     if peekEnabled()
       $(this).trigger 'peek:update'


Update it.coffee

Update it.coffee

--- app/locale/it.coffee
+++ app/locale/it.coffee
@@ -247,7 +247,7 @@
     victory_saving_progress: "Salvataggio progressi"
     victory_go_home: "Torna alla pagina iniziale"
     victory_review: "Dicci di più!"
-#    victory_review_placeholder: "How was the level?"
+    victory_review_placeholder: "Come è stato il livello?"
     victory_hour_of_code_done: "Finito?"
     victory_hour_of_code_done_yes: "Si, ho finito la mia ora di programmazione!"
     victory_experience_gained: "Punti XP guadagnati"
@@ -322,7 +322,7 @@
     tip_brute_force: "Quando sei in dubbio, usa la forza bruta. - Ken Thompson"
     tip_extrapolation: "Ci sono soltanto due tipi di persone: quelli che possono estrapolare da dati incompleti..."
     tip_superpower: "La programmazione è la cosa che più si avvicina a un superpotere."
-#    tip_control_destiny: "In real open source, you have the right to control your own destiny. - Linus Torvalds"
+    tip_control_destiny: "Nel vero open source, hai il diritto di controllare il propio destino. - Linus Torvalds"
     tip_no_code: "Nessun codice è più veloce di nessun codice."
     tip_code_never_lies: "Il codice non mente mai, ma i commenti a volte lo fanno. — Ron Jeffries"
 #    tip_reusable_software: "Before software can be reusable it first has to be usable."
@@ -412,14 +412,14 @@
 #    unsubscribe: "Unsubscribe"
 #    confirm_unsubscribe: "Confirm Unsubscribe"
 #    never_mind: "Never Mind, I Still Love You"
-#    thank_you_months_prefix: "Thank you for supporting us these last"
-#    thank_you_months_suffix: "months."
-#    thank_you: "Thank you for supporting CodeCombat."
+#    thank_you_months_prefix: "Grazie for supporting us these last"
+    thank_you_months_suffix: "mesi."
+#    thank_you: "Grazie for supporting CodeCombat."
 #    sorry_to_see_you_go: "Sorry to see you go! Please let us know what we could have done better."
 #    unsubscribe_feedback_placeholder: "O, what have we done?"
-#    parent_button: "Ask your parent"
+    parent_button: "Chiedi i tuoi genitori"
 #    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
-#    parent_email_input_invalid: "Email address invalid."
+#    parent_email_input_invalid: "Indirizzo Email invalido."
 #    parent_email_input_label: "Parent email address"
 #    parent_email_input_placeholder: "Enter parent email"
     parent_email_send: "Invia email"
@@ -428,17 +428,17 @@
     parents: "Per i genitori"
 #    parents_title: "Dear Parent: Your child is learning to code. Will you help them continue?"
 #    parents_blurb1: "Your child has played __nLevels__ levels and learned programming basics. Help cultivate their interest and buy them a subscription so they can keep playing."
-#    parents_blurb1a: "Computer programming is an essential skill that your child will undoubtedly use as an adult. By 2020, basic software skills will be needed by 77% of jobs, and software engineers are in high demand across the world. Did you know that Computer Science is the highest-paid university degree?"
+#    parents_blurb1a: "Computer programming è una capacità essenziale che your child will undoubtedly use as an adult. By 2020, basic software skills will be needed by 77% of jobs, and software engineers are in high demand across the world. Did you know that Computer Science is the highest-paid university degree?"
 #    parents_blurb2: "For $9.99 USD/mo, your child will get new challenges every week and personal email support from professional programmers."
-#    parents_blurb3: "No Risk: 100% money back guarantee, easy 1-click unsubscribe."
-#    payment_methods: "Payment Methods"
-#    payment_methods_title: "Accepted Payment Methods"
+#    parents_blurb3: "Senza Rischio: 100% money back guarantee, easy 1-click unsubscribe."
+    payment_methods: "Metodi di Pagamento"
+    payment_methods_title: "Metodi di Pagamento Accetati"
 #    payment_methods_blurb1: "We currently accept credit cards and Alipay."
 #    payment_methods_blurb2: "If you require an alternate form of payment, please contact"
-#    stripe_description: "Monthly Subscription"
+    stripe_description: "Sottoscrizione mensile"
 #    subscription_required_to_play: "You'll need a subscription to play this level."
-#    unlock_help_videos: "Subscribe to unlock all video tutorials."
-#    personal_sub: "Personal Subscription" # Accounts Subscription View below
+#    unlock_help_videos: "Sottoscribe to unlock all video tutorials."
+    personal_sub: "Sottoscrizione Personale" # Accounts Subscription View below
 #    loading_info: "Loading subscription information..."
 #    managed_by: "Managed by"
 #    will_be_cancelled: "Will be cancelled on"
@@ -453,13 +453,13 @@
 #    group_discounts_1st: "1st subscription"
 #    group_discounts_full: "Full price"
 #    group_discounts_2nd: "Subscriptions 2-11"
-#    group_discounts_20: "20% off"
+    group_discounts_20: "20% disconto"
 #    group_discounts_12th: "Subscriptions 12+"
-#    group_discounts_40: "40% off"
+    group_discounts_40: "40% di sconto"
 #    subscribing: "Subscribing..."
 #    recipient_emails_placeholder: "Enter email address to subscribe, one per line."
-#    subscribe_users: "Subscribe Users"
-#    users_subscribed: "Users subscribed:"
+    subscribe_users: "Sottoscrivere Utenti"
+    users_subscribed: "Utenti sottoscritti:"
 #    no_users_subscribed: "No users subscribed, please double check your email addresses."
 #    current_recipients: "Current Recipients"
 #    unsubscribing: "Unsubscribing..."
@@ -583,7 +583,7 @@
     retrostyle_blurb: "Giochi retrò"

 #  teachers:
-#    title: "CodeCombat: Info for Teachers"
+    title: "CodeCombat: Informazzione per Professori"
 #    intro_1: "CodeCombat is an online game that teaches programming. Students write code in real programming languages."
 #    intro_2: "No experience required!"
 #    free_title: "How much does it cost?"
@@ -597,7 +597,7 @@
 #    sub_includes_title: "What is included in the subscription?"
 #    sub_includes_1: "In addition to the 100+ basic levels, students with a monthly subscription get access to these additional features:"
 #    sub_includes_2: "70+ practice levels"
-#    sub_includes_3: "Video tutorials"
+    sub_includes_3: "Video tutoriali"
 #    sub_includes_4: "Premium email support"
 #    sub_includes_5: "10 new heroes with unique skills to master"
 #    sub_includes_6: "3500 bonus gems every month"
@@ -610,7 +610,7 @@
 #    monitor_progress_5: "After they join, you will see a summary of the student's progress on your Clan's page."
 #    private_clans_1: "Private Clans provide increased privacy and detailed progress information for each student."
 #    private_clans_2: "To create a private Clan, check the 'Make clan private' checkbox when creating a"
-#    private_clans_3: "."
+    private_clans_3: "."
 #    who_for_title: "Who is CodeCombat for?"
 #    who_for_1: "We recommend CodeCombat for students aged 9 and up. No prior programming experience is needed."
 #    who_for_2: "We've designed CodeCombat to appeal to both boys and girls."
@@ -626,9 +626,9 @@
 #    how_much_5: "We accept discounted one-time purchases and yearly subscription purchases for groups, such as a class or school.  Please contact"
 #    how_much_6: "for more details."
 #    more_info_title: "Where can I find more information?"
-#    more_info_1: "Our"
+    more_info_1: "Il nostro"
 #    more_info_2: "teachers forum"
-#    more_info_3: "is a good place to connect with fellow educators who are using CodeCombat."
+#    more_info_3: "è un buon posto to connect with fellow educators who are using CodeCombat."
 #    sys_requirements_title: "System Requirements"
 #    sys_requirements_1: "A modern web browser. Newer versions of Chrome, Firefox, or Safari. Internet Explorer 9 or later."
 #    sys_requirements_2: "CodeCombat is not supported on iPad yet."
@@ -647,15 +647,15 @@
 #    contact_1: "Please contact"
 #    contact_2: "if you have further questions."
 #    description_1: "We offer free subscriptions to teachers for evaluation purposes. You can find more information on our"
-#    description_2: "teachers"
+    description_2: "professori"
 #    description_3: "page."
 #    description_4: "Please fill out this quick survey and we’ll email you setup instructions."
 #    email: "Email Address"
-#    school: "Name of School"
-#    location: "Name of City"
+    school: "Nome di Scuola"
+    location: "Nome di Città"
 #    age_students: "How old are your students?"
 #    under: "Under"
-#    other: "Other:"
+    other: "Altri:"
 #    amount_students: "How many students do you teach?"
 #    hear_about: "How did you hear about CodeCombat?"
 #    fill_fields: "Please fill out all fields."
@@ -806,7 +806,7 @@
 #    progress: "Progress"
 #    not_started_1: "not started"
 #    started_1: "started"
-#    complete_1: "complete"
+    complete_1: "finito"
 #    exp_levels: "Expand levels"
 #    rem_hero: "Remove Hero"
 #    status: "Status"
@@ -883,7 +883,7 @@
     config_thang: "Doppio click per configurare un thang"
     delete: "Cancella"
     duplicate: "Duplica"
-#    stop_duplicate: "Stop Duplicate"
+    stop_duplicate: "Ferma Duplicazzione"
     rotate: "Ruota"
     level_settings_title: "Impostazioni"
     level_component_tab_title: "Componenti esistenti"
@@ -894,7 +894,7 @@
     level_components_title: "Torna all'elenco thangs"
     level_components_type: "Tipo"
     level_component_edit_title: "Modifica componente"
-#    level_component_config_schema: "Config Schema"
+    level_component_config_schema: "Schema di configurazione"
     level_component_settings: "Impostazioni"
     level_system_edit_title: "Modifica sistema"
     create_system_title: "Crea nuovo sistema"
@@ -1039,21 +1039,21 @@
     tutorial_play_first: "Prima di tutto gioca al Tutorial."
     simple_ai: "IA semplice"
     warmup: "Allenamento"
-#    friends_playing: "Friends Playing"
+    friends_playing: "Amici Giocando"
     log_in_for_friends: "Accedi per giocare con i tuoi amici!"
     social_connect_blurb: "Connettiti e gioca contro i tuoi amici!"
     invite_friends_to_battle: "Invita i tuoi amici a unirsi a te nella battaglia!"
     fight: "Combatti!"
 #    watch_victory: "Watch your victory"
 #    defeat_the: "Defeat the"
-#    tournament_started: ", started"
-#    tournament_ends: "Tournament ends"
+    tournament_started: ", ha cominciato"
+    tournament_ends: "Torneo conclude"
     tournament_ended: "Torneo concluso"
     tournament_rules: "Regole torneo"
 #    tournament_blurb: "Write code, collect gold, build armies, crush foes, win prizes, and upgrade your career in our $40,000 Greed tournament! Check out the details"
 #    tournament_blurb_criss_cross: "Win bids, construct paths, outwit opponents, grab gems, and upgrade your career in our Criss-Cross tournament! Check out the details"
 #    tournament_blurb_zero_sum: "Unleash your coding creativity in both gold gathering and battle tactics in this alpine mirror match between red sorcerer and blue sorcerer. The tournament began on Friday, March 27 and will run until Monday, April 6 at 5PM PDT. Compete for fun and glory! Check out the details"
-#    tournament_blurb_blog: "on our blog"
+    tournament_blurb_blog: "nel nostro blog"
     rules: "Regole"
     winners: "Vincitori"

@@ -1148,7 +1148,7 @@
     gplus_friends: "Amici G+"
     gplus_friend_sessions: "Sessioni amici G+"
     leaderboard: "Classifica"
-#    user_schema: "User Schema"
+    user_schema: "Schema Utenti"
     user_profile: "Profilo utente"
 #    patch: "Patch"
 #    patches: "Patches"
@@ -1190,7 +1190,7 @@

 #  concepts:
 #    advanced_strings: "Advanced Strings"
-#    algorithms: "Algorithms"
+    algorithms: "Algoritmi"
 #    arguments: "Arguments"
 #    arithmetic: "Arithmetic"
 #    arrays: "Arrays"
@@ -1318,14 +1318,14 @@
 #    not_featured: "Not Featured"
 #    looking_for: "Looking for:"
     last_updated: "Ultimo aggiornamento:"
-#    contact: "Contact"
+    contact: "Contatto"
 #    active: "Looking for interview offers now"
 #    inactive: "Not looking for offers right now"
 #    complete: "complete"
 #    next: "Next"
-#    next_city: "city?"
+    next_city: "città?"
 #    next_country: "pick your country."
-#    next_name: "name?"
+    next_name: "nome?"
 #    next_short_description: "write a short description."
 #    next_long_description: "describe your desired position."
 #    next_skills: "list at least five skills."
@@ -1335,7 +1335,7 @@
 #    next_links: "add any personal or social links."
 #    next_photo: "add an optional professional photo."
 #    next_active: "mark yourself open to offers to show up in searches."
-#    example_blog: "Blog"
+    example_blog: "Blog"
 #    example_personal_site: "Personal Site"
 #    links_header: "Personal Links"
 #    links_blurb: "Link any other sites or profiles you want to highlight, like your GitHub, your LinkedIn, or your blog."
@@ -1347,9 +1347,9 @@
 #    basics_active_help: "Want interview offers right now?"
 #    basics_job_title: "Desired Job Title"
 #    basics_job_title_help: "What role are you looking for?"
-#    basics_city: "City"
+    basics_city: "Città"
 #    basics_city_help: "City you want to work in (or live in now)."
-#    basics_country: "Country"
+    basics_country: "Paese"
 #    basics_country_help: "Country you want to work in (or live in now)."
 #    basics_visa: "US Work Status"
 #    basics_visa_help: "Are you authorized to work in the US, or do you need visa sponsorship? (If you live in Canada or Australia, mark authorized.)"
@@ -1382,14 +1382,14 @@
 #    work_employer_help: "Name of your employer."
 #    work_role: "Job Title"
 #    work_role_help: "What was your job title or role?"
-#    work_duration: "Duration"
+    work_duration: "Durazione"
 #    work_duration_help: "When did you hold this gig?"
 #    work_description: "Description"
 #    work_description_help: "What did you do there? (140 chars; optional)"
 #    education: "Education"
 #    education_header: "Recount your academic ordeals"
 #    education_blurb: "List your academic ordeals."
-#    education_school: "School"
+    education_school: "Scuola"
 #    education_school_help: "Name of your school."
 #    education_degree: "Degree"
 #    education_degree_help: "What was your degree and field of study?"
@@ -1409,7 +1409,7 @@
 #    project_description_help: "Briefly describe the project."
 #    project_picture: "Picture"
 #    project_picture_help: "Upload a 230x115px or larger image showing off the project."
-#    project_link: "Link"
+    project_link: "Link"
 #    project_link_help: "Link to the project."
 #    player_code: "Player Code"

@@ -1447,16 +1447,16 @@
 #    what_blurb: "CodeCombat is a multiplayer browser programming game. Players write code to control their forces in battle against other developers. Our players have experience with all major tech stacks."
 #    cost: "How much do we charge?"
 #    cost_blurb: "We charge 15% of first year's salary and offer a 100% money back guarantee for 90 days. We don't charge for candidates who are already actively being interviewed at your company."
-#    candidate_name: "Name"
+    candidate_name: "Nome"
 #    candidate_location: "Location"
 #    candidate_looking_for: "Looking For"
 #    candidate_role: "Role"
 #    candidate_top_skills: "Top Skills"
 #    candidate_years_experience: "Yrs Exp"
 #    candidate_last_updated: "Last Updated"
-#    candidate_who: "Who"
+    candidate_who: "Chi"
 #    featured_developers: "Featured Developers"
-#    other_developers: "Other Developers"
+    other_developers: "Altri Sviluppatori"
 #    inactive_developers: "Inactive Developers"

   admin:


Update sk.coffee

Update sk.coffee

clans

--- app/locale/sk.coffee
+++ app/locale/sk.coffee
@@ -1076,28 +1076,28 @@
     saved: "Zmeny uložené"
     password_mismatch: "Heslá nesedia."
     password_repeat: "Zopakuj, prosím, svoje heslo."
-#    file_size_limit: "(max file size: 1MB)"
-
-#  keyboard_shortcuts:
-#    keyboard_shortcuts: "Keyboard Shortcuts"
-#    space: "Space"
-#    enter: "Enter"
-#    press_enter: "press enter"
-#    escape: "Escape"
-#    shift: "Shift"
-#    run_code: "Run current code."
-#    run_real_time: "Run in real time."
-#    continue_script: "Continue past current script."
-#    skip_scripts: "Skip past all skippable scripts."
-#    toggle_playback: "Toggle play/pause."
-#    scrub_playback: "Scrub back and forward through time."
-#    single_scrub_playback: "Scrub back and forward through time by a single frame."
-#    scrub_execution: "Scrub through current spell execution."
-#    toggle_debug: "Toggle debug display."
-#    toggle_grid: "Toggle grid overlay."
-#    toggle_pathfinding: "Toggle pathfinding overlay."
-#    beautify: "Beautify your code by standardizing its formatting."
-#    maximize_editor: "Maximize/minimize code editor."
+    file_size_limit: "(max veľkosť súboru: 1MB)"
+
+  keyboard_shortcuts:
+    keyboard_shortcuts: "Klávesové skratky"
+    space: "Medzerník"
+    enter: "Enter"
+    press_enter: "stlač enter"
+    escape: "Esc"
+    shift: "Shift"
+    run_code: "Spusti aktuálny kód."
+    run_real_time: "Spusti v reálnom čase."
+    continue_script: "Pokračuj v aktuálnom skripte."
+    skip_scripts: "Preskoč všetky skripty."
+    toggle_playback: "Prepni play/pause."
+    scrub_playback: "Presúvaj sa dopredu/dozadu."
+    single_scrub_playback: "Presúvaj sa dopredu/dozadu o jeden snímok."
+    scrub_execution: "Presúvaj sa aktuálnym kúzlom."
+    toggle_debug: "Prepni ladenie."
+    toggle_grid: "Prepni zobrazenie mriežky."
+    toggle_pathfinding: "Prepni zobrazenie hľadania cesty."
+    beautify: "Skrášli kód pomocou štandardizácie formátovania."
+    maximize_editor: "Zväčši maximálne/minimálne editor."

   community:
     main_title: "CodeCombat Komunita"
@@ -1142,36 +1142,36 @@
     average_level: "Priemerná úroveň"
     average_achievements: "Priemerný výkon"
     delete_clan: "Vymaž klan"
-#    leave_clan: "Leave Clan"
-#    join_clan: "Join Clan"
-#    invite_1: "Invite:"
-#    invite_2: "*Invite players to this Clan by sending them this link."
-#    members: "Members"
-#    progress: "Progress"
-#    not_started_1: "not started"
-#    started_1: "started"
-#    complete_1: "complete"
-#    exp_levels: "Expand levels"
-#    rem_hero: "Remove Hero"
-#    status: "Status"
-#    complete_2: "Complete"
-#    started_2: "Started"
-#    not_started_2: "Not Started"
-#    view_solution: "Click to view solution."
-#    view_attempt: "Click to view attempt."
-#    latest_achievement: "Latest Achievement"
-#    playtime: "Playtime"
-#    last_played: "Last played"
-#    leagues_explanation: "Play in a league against other clan members in these multiplayer arena instances."
-#    track_concepts1: "Track concepts"
-#    track_concepts2a: "learned by each student"
-#    track_concepts2b: "learned by each member"
-#    track_concepts3a: "Track levels completed for each student"
-#    track_concepts3b: "Track levels completed for each member"
-#    track_concepts4a: "See your students'"
-#    track_concepts4b: "See your members'"
-#    track_concepts5: "solutions"
-#    track_concepts6a: "Sort students by name or progress"
+    leave_clan: "Opusti klan"
+    join_clan: "Pridaj sa do klanu"
+    invite_1: "Pozvi:"
+    invite_2: "*Pozvi hráčov do klanu, tak že im pošleš tento odkaz."
+    members: "Členovia"
+    progress: "Postup"
+    not_started_1: "nezačal"
+    started_1: "začal"
+    complete_1: "ukončené"
+    exp_levels: "Úrovne"
+    rem_hero: "Odstráň hrdinu"
+    status: "Status"
+    complete_2: "ukončené"
+    started_2: "začal"
+    not_started_2: "nezačal"
+    view_solution: "Klikni a pozri si riešenie."
+    view_attempt: "Klikni a pozri si pokus."
+    latest_achievement: "Posledný úspech"
+    playtime: "Čas hry"
+    last_played: "Naposledy hrané"
+    leagues_explanation: "Hraj ligu s inými členmi klanu v multiplayerových leveloch."
+    track_concepts1: "Sleduj koncepcie"
+    track_concepts2a: "zvládnuté každým študentom"
+    track_concepts2b: "zvládnuté každým členom"
+    track_concepts3a: "Sleduj úrovne ukončené študentmi"
+    track_concepts3b: "Sleduj úrovne ukončené členmi"
+    track_concepts4a: "Pozri študentské'"
+    track_concepts4b: "Pozri členské'"
+    track_concepts5: "riešenia"
+    track_concepts6a: "Usporiadaj študentov podľa mena alebo postupu"
 #    track_concepts6b: "Sort members by name or progress"
 #    track_concepts7: "Requires invitation"
 #    track_concepts8: "to join"


Fix build

Fix build

--- app/locale/sr.coffee
+++ app/locale/sr.coffee
@@ -54,7 +54,7 @@
     play_as: "Играј као" # Ladder page
     compete: "Такмичи се!" # Course details page
     spectate: "Посматрај" # Ladder page
-   players: "играчи" # Hover over a level on /play
+    players: "играчи" # Hover over a level on /play
     hours_played: "потребно сати играња" # Hover over a level on /play
     items: "Ствари" # Tooltip on item shop button from /play
     unlock: "Откључај" # For purchasing items and heroes
@@ -64,7 +64,7 @@
     purchasable: "Могуће купити" # For a hero you unlocked but haven't purchased
     available: "Доступни"
     skills_granted: "Обезбеђени скилови" # Property documentation details
-   heroes: "Хероји" # Tooltip on hero shop button from /play
+    heroes: "Хероји" # Tooltip on hero shop button from /play
     achievements: "Достигнућа" # Tooltip on achievement list button from /play
     account: "Налог" # Tooltip on account button from /play
     settings: "Подешавања" # Tooltip on settings button from /play
@@ -123,7 +123,7 @@
     send_password: "Пошаљи помоћну шифру"
     recovery_sent: "Послат мејл за потврду опоравка."

-#  items:
+  items:
     primary: "Примарни"
     secondary: "Секундарни"
     armor: "Оклоп"
@@ -141,7 +141,7 @@
     cancel: "Откажи"
     save: "Сачувај"
     publish: "Објави"
-   create: "Створи"
+    create: "Створи"
 #    fork: "Fork"
     play: "Нивои" # When used as an action verb, like "Play next level"
 #    retry: "Retry"
@@ -150,7 +150,7 @@
     help: "Помоћ"
     watch: "Одгледај"
     unwatch: "Не гледај"
-   submit_patch: "Потврди закрпу"
+    submit_patch: "Потврди закрпу"
     submit_changes: "Потврди измене"
     save_changes: "Сачувај измене"

@@ -268,7 +268,7 @@
 #    tome_cast_button_run: "Run"
 #    tome_cast_button_running: "Running"
 #    tome_cast_button_ran: "Ran"
-#    tome_submit_button: "Потврди"
+    tome_submit_button: "Потврди"
 #    tome_reload_method: "Reload original code for this method" # Title text for individual method reload button.
 #    tome_select_method: "Select a Method"
 #    tome_see_all_methods: "See all methods you can edit" # Title text for method list selector (shown when there are multiple programmable methods).
@@ -374,13 +374,13 @@
 #    difficulty: "Difficulty"
 #    gold_collected: "Gold Collected"

-#  inventory:
+  inventory:
     equipped_item: "Опремљен"
     required_purchase_title: "Потребно"
     available_item: "Доступно"
     restricted_title: "Забрањено"
     should_equip: "(Дупли-клик за опремање)"
-   equipped: "(опремљен/а)"
+    equipped: "(опремљен/а)"
 #    locked: "(locked)"
 #    restricted: "(restricted in this level)"
     equip: "Опреми"


Fix uk.locale

Fix uk.locale

--- app/locale/uk.coffee
+++ app/locale/uk.coffee
@@ -776,7 +776,7 @@
     number: "Число"
     webjavascript: "JavaScript"

-  amazon_hoc:
+#   amazon_hoc:
 #    title: "Keep Learning with Amazon!"
 #    congrats: "Congratulations on conquering that challenging Hour of Code!"
 #    educate_1: "Now, keep learning about coding and cloud computing with AWS Educate, an exciting, free program from Amazon for both students and teachers. With AWS Educate, you can earn cool badges as you learn about the basics of the cloud and cutting-edge technologies such as gaming, virtual reality, and Alexa."


Allow cookie setting in cross domain login redirects (#5587)

--- app/models/User.coffee
+++ app/models/User.coffee
@@ -419,6 +419,7 @@
     options.data ?= {}
     _.extend(options.data, {name, email, password})
     options.contentType = 'application/json'
+    options.xhrFields = { withCredentials: true }
     options.data = JSON.stringify(options.data)
     jqxhr = @fetch(options)
     jqxhr.then ->
@@ -431,6 +432,7 @@
     options.data ?= {}
     _.extend(options.data, {name, email, facebookID, facebookAccessToken: application.facebookHandler.token()})
     options.contentType = 'application/json'
+    options.xhrFields = { withCredentials: true }
     options.data = JSON.stringify(options.data)
     jqxhr = @fetch(options)
     jqxhr.then ->
@@ -444,6 +446,7 @@
     options.data ?= {}
     _.extend(options.data, {name, email, gplusID, gplusAccessToken: application.gplusHandler.token()})
     options.contentType = 'application/json'
+    options.xhrFields = { withCredentials: true }
     options.data = JSON.stringify(options.data)
     jqxhr = @fetch(options)
     jqxhr.then ->
@@ -460,6 +463,7 @@
   loginGPlusUser: (gplusID, options={}) ->
     options.url = '/auth/login-gplus'
     options.type = 'POST'
+    options.xhrFields = { withCredentials: true }
     options.data ?= {}
     options.data.gplusID = gplusID
     options.data.gplusAccessToken = application.gplusHandler.token()
@@ -474,12 +478,14 @@
   loginFacebookUser: (facebookID, options={}) ->
     options.url = '/auth/login-facebook'
     options.type = 'POST'
+    options.xhrFields = { withCredentials: true }
     options.data ?= {}
     options.data.facebookID = facebookID
     options.data.facebookAccessToken = application.facebookHandler.token()
     @fetch(options)

   loginPasswordUser: (usernameOrEmail, password, options={}) ->
+    options.xhrFields = { withCredentials: true }
     options.url = '/auth/login'
     options.type = 'POST'
     options.data ?= {}


Wrong attribute name in last check

Wrong attribute name in last check

--- app/models/post.coffee
+++ app/models/post.coffee
@@ -160,7 +160,7 @@
 			$clean: (str, body, user) ->
 				str = validator.stripLow(str, true)
 				# remove images
-				unless user.flags.isEditor
+				unless user.flags.editor
 					str = str.replace /(!\[.*?\]\()(.+?)(\))/g, (whole, a, url, c) ->
 						console.log whole, url
 						# TODO check if user owns this pic


refactored coffeescript

refactored coffeescript

--- drm-validation.coffee
+++ drm-validation.coffee
@@ -1,532 +1,595 @@
 ###############################################################################
 # Client-side form validation
 ###############################################################################
+###
+jshint -W100
+###
+"use strict"

 ( ($) ->
-
-    drmForms = {
-
-        config: {
-            speed: 300
-        }
-
-        init: (config) ->
-            $.extend @.config, config
+    class DrmForms
+        constructor: (@speed = 300) ->
+            self = @
             body = $ 'body'
+
+            validateField = (value, validate) ->
+                if validate.message isnt null
+                    self.issueNotice.call @, validate.status, validate.message, validate.issuer, self.speed
+                else
+                    self.removeNotice.call @, validate.issuer, self.speed
+                self.removeValidationClass.call @, validate.status
+                self.applyValidationClass.call @, validate.status

             body.on 'click', ':disabled', (e) ->
                 e.preventDefault()

-            body.on 'keyup', '.drm-valid-integer', @validateInteger
-            body.on 'keyup', '.drm-valid-number', @validateNumber
-            body.on 'keyup', '.drm-valid-url', @validateURL
-            body.on 'keyup', '.drm-valid-phone', @validatePhone
-            body.on 'keyup', '.drm-valid-email', @validateEmail
-            body.on 'keyup', '.drm-valid-full-name', @validateFullName
-            body.on 'keyup', '.drm-valid-alpha', @validateAlpha
-            body.on 'keyup', '.drm-valid-alphanum', @validateAlphaNum
-            body.on 'keyup', '.drm-valid-alphadash', @validateAlphaNumDash
-            body.on 'keyup', '.drm-valid-alpha-num-underscore', @validateAlphaNumUnderscore
-            body.on 'keyup', '.drm-valid-no-spaces', @validateNoSpaces
-            body.on 'keyup', '.drm-valid-no-tags', @validateNoTags
-            body.on 'keyup', '[required]', @validateRequired
-            body.on 'keyup', ':input[data-max-value]:not([data-min-value])', @validateMaxValue
-            body.on 'keyup', ':input[data-min-value]:not([data-max-value])', @validateMinValue
-            body.on 'keyup', ':input[data-max-length]:not([data-min-length])', @validateMaxLength
-            body.on 'keyup', ':input[data-min-length]:not([data-max-length])', @validateMinLength
-            body.on 'keyup', ':input[data-min-value][data-max-value]', @validateBetweenValue
-            body.on 'keyup', ':input[data-min-length][data-max-length]', @validateBetweenLength
-            body.on 'keyup', ':input', @trackLength
-            body.on 'blur', ':input:not([required])', @validateEmpty
-
-        success: ->
-            that = $ @
-            drmForms.removeValidationClass.call that
-            that.addClass 'drm-form-success'
-
-        warning: ->
-            that = $ @
-            drmForms.removeValidationClass.call that
-            that.addClass 'drm-form-warning'
-
-        danger: ->
-            that = $ @
-            drmForms.removeValidationClass.call that
-            that.addClass 'drm-form-danger'
+            body.on 'keyup', ':input.drm-valid-integer', ->
+                value = self.getValue.call @
+                validate = self.validateInteger value
+                validateField.call @, value, validate
+
+            body.on 'keyup', ':input.drm-valid-number', ->
+                value = self.getValue.call @
+                validate = self.validateNumber value
+                validateField.call @, value, validate
+
+            body.on 'keyup', ':input.drm-valid-url', ->
+                value = self.getValue.call @
+                validate = self.validateURL value
+                validateField.call @, value, validate
+
+            body.on 'keyup', ':input.drm-valid-phone', ->
+                value = self.getValue.call @
+                validate = self.validatePhone value
+                validateField.call @, value, validate
+
+            body.on 'keyup', ':input.drm-valid-email', ->
+                value = self.getValue.call @
+                validate = self.validateEmail value
+                validateField.call @, value, validate
+
+            body.on 'keyup', ':input.drm-valid-full-name', ->
+                value = self.getValue.call @
+                validate = self.validateFullName value
+                validateField.call @, value, validate
+
+            body.on 'keyup', ':input.drm-valid-alpha', ->
+                value = self.getValue.call @
+                validate = self.validateAlpha value
+                validateField.call @, value, validate
+
+            body.on 'keyup', ':input.drm-valid-alphanum', ->
+                value = self.getValue.call @
+                validate = self.validateAlphaNum value
+                validateField.call @, value, validate
+
+            body.on 'keyup', ':input.drm-valid-alphadash', ->
+                value = self.getValue.call @
+                validate = self.validateAlphaNumDash value
+                validateField.call @, value, validate
+
+            body.on 'keyup', ':input.drm-valid-alpha-num-underscore', ->
+                value = self.getValue.call @
+                validate = self.validateAlphaNumUnderscore value
+                validateField.call @, value, validate
+
+            body.on 'keyup', ':input.drm-valid-no-spaces', ->
+                value = self.getValue.call @
+                validate = self.validateNoSpaces value
+                validateField.call @, value, validate
+
+            body.on 'keyup', ':input.drm-valid-no-tags', ->
+                value = self.getValue.call @
+                validate = self.validateNoTags value
+                validateField.call @, value, validate
+
+            body.on 'keyup', ':input[data-max-value]:not([data-min-value])', ->
+                value = self.getValue.call @
+                validate = self.validateMaxValue value
+                validateField.call @, value, validate
+
+            body.on 'keyup', ':input[data-min-value]:not([data-max-value])', ->
+                value = self.getValue.call @
+                validate = self.validateMinValue value
+                validateField.call @, value, validate
+
+            body.on 'keyup', ':input[data-max-length]:not([data-min-length])', ->
+                value = self.getValue.call @
+                validate = self.validateMaxLength value
+                validateField.call @, value, validate
+
+            body.on 'keyup', ':input[data-min-length]:not([data-max-length])', ->
+                value = self.getValue.call @
+                validate = self.validateMinLength value
+                validateField.call @, value, validate
+
+            body.on 'keyup', ':input[data-min-value][data-max-value]', ->
+                value = self.getValue.call @
+                validate = self.validateBetweenValue value
+                validateField.call @, value, validate
+
+            body.on 'keyup', ':input[data-min-length][data-max-length]', ->
+                value = self.getValue.call @
+                validate = self.validateBetweenLength value
+                validateField.call @, value, validate
+
+            body.on 'keyup', ':input', self.trackLength
+
+            body.on 'keyup', '[required]', ->
+                value = self.getValue.call @
+                validate = self.validateRequired value
+                validateField.call @, value, validate
+
+            body.on 'blur', ':input:not([required])', ->
+                value = self.getValue.call @
+                if not value
+                    self.removeValidationClass.call @
+                    self.removeAllNotices.call @

         trackLength: ->
             that = $ @
             value = $.trim that.val()
             length = value.length
-            lengthNotice = $ ".form-length-notice"
+            lengthNotice = that.nextUntil ':input', '.form-length-notice'
+
             createMessage = (length) ->
-                message = if length == 1 then "#{length} character" else "#{length} characters"
-                return message
-
-            if lengthNotice.length == 0
-                message = createMessage(length)
-                lengthNotice = $ '

', {
+                message = if length is 1 then "#{length} character" else "#{length} characters"
+                message
+
+            if lengthNotice.length is 0
+                message = createMessage length
+                lengthNotice = $ '

',
                     text: message
                     class: 'form-length-notice'
-                }

                 lengthNotice.hide().insertAfter(that).show()
-            else if length == 0
+            else if length is 0
                 lengthNotice.remove()
             else
-                message = createMessage(length)
+                message = createMessage length
                 lengthNotice.text message

-        issueNotice: (status, message) ->
-            that = $ @
-            notice = $ ".form-#{status}-notice:contains(#{message})"
-
-            if notice.length == 0
-                notice = $ '

', {
+        getValue: ->
+            value = $.trim $(@).val()
+            value
+
+        issueNotice: (status, message, issuer, speed) ->
+            that = $ @
+            notice = $ "p.form-#{status}-notice:contains(#{message})"
+
+            if notice.length is 0
+                notice = $ '

',
                     text: message,
                     class: "form-#{status}-notice"
-                }
+                    'data-issuer': issuer

-                notice.hide().insertAfter(that).slideDown drmForms.config.speed
-
-        removeNotice: (status, message) ->
-            notice = $ ".form-#{status}-notice:contains(#{message})"
-            notice.slideUp drmForms.config.speed, ->
+                notice.hide().insertAfter(that).slideDown speed
+
+        removeNotice: (issuer, speed) ->
+            notice = $ "p[data-issuer='#{issuer}']"
+            notice.slideUp speed, ->
+                $(@).remove()
+
+        removeAllNotices: ->
+            that = $ @
+            notices = that.nextUntil ':input','p.form-success-notice, p.form-warning-notice, p.form-danger-notice'
+            notices.slideUp @speed, ->
                 $(@).remove()

-        removeAllNotices: ->
-            that = $ @
-            notices = that.nextUntil(':input','.form-success-notice, .form-warning-notice, .form-danger-notice')
-            notices.slideUp drmForms.config.speed, ->
-                $(@).remove()
-
         applyValidationClass: (status) ->
             that = $ @
             switch status
-                when 'danger' then drmForms.danger.call that
-                when 'warning' then drmForms.warning.call that
-                when 'success' then drmForms.success.call that
-
-        removeValidationClass: ->
-            that = $ @
-            that.removeClass 'drm-form-danger'
-            that.removeClass 'drm-form-warning'
-            that.removeClass 'drm-form-success'
+                when 'danger' then that.addClass 'drm-form-danger'
+                when 'warning' then that.addClass 'drm-form-warning'
+                when 'success' then that.addClass 'drm-form-success'
+
+        removeValidationClass: (status) ->
+            that = $ @
+
+            switch status
+                when 'danger'
+                    that.removeClass 'drm-form-warning'
+                    that.removeClass 'drm-form-success'
+                when 'warning'
+                    that.removeClass 'drm-form-danger'
+                    that.removeClass 'drm-form-success'
+                when 'success'
+                    that.removeClass 'drm-form-danger'
+                    that.removeClass 'drm-form-warning'
+                else
+                    that.removeClass 'drm-form-danger'
+                    that.removeClass 'drm-form-warning'
+                    that.removeClass 'drm-form-success'

         validateEmpty: ->
             that = $ @
             value = $.trim that.val()

-            if !value
-                drmForms.removeValidationClass.call that
-                drmForms.removeAllNotices.call that
-
-        validateRequired: ->
-            that = $ @
-            status = null
-            value = $.trim that.val()
-            message = 'this field is required'
-
-            if value.length == 0
-                status = 'danger'
-                drmForms.issueNotice.call(that, status, message)
-            else
-                drmForms.removeNotice('danger', message)
-
-            drmForms.applyValidationClass.call(that, status)
-
-        validateInteger: ->
-            that = $ @
-            status = null
-            value = $.trim that.val()
+            if not value
+                @removeValidationClass.call that
+                @removeAllNotices.call that
+
+        validateRequired: (value) ->
+            validate =
+                status: null
+                message: null
+                issuer: 'required'
+
+            if value.length is 0
+                validate.message = 'this field is required'
+                validate.status = 'danger'
+
+            validate
+
+        validateInteger: (value) ->
+            validate =
+                status: null
+                message: null
+                issuer: 'integer'
             # an integer can be negative or positive and can include one comma separator followed by exactly 3 numbers
             re = new RegExp "^\\-?\\d*"

             evaluate = (result, value) ->
-                message = 'please enter a valid integer'
-                if result and value == result
-                    status = 'success'
-                    drmForms.removeNotice('danger', message)
-                else
-                    status = 'danger'
-                    drmForms.issueNotice.call(that, status, message)
-                return status
-
-            if value
-                result = $.trim(re.exec value)
-                status = evaluate(result, value)
-
-            drmForms.applyValidationClass.call(that, status)
-
-        validateNumber: ->
-            that = $ @
-            status = null
-            value = $.trim that.val()
+                if result and value is result
+                    validate.status = 'success'
+                else
+                    validate.message = 'please enter a valid integer'
+                    validate.status = 'danger'
+                validate.status
+
+            if value?
+                result = $.trim re.exec value
+                validate.status = evaluate result, value
+
+            validate
+
+        validateNumber: (value) ->
+            validate =
+                status: null
+                message: null
+                issuer: 'number'
             re = new RegExp "^\\-?\\d*\\.?\\d*"

             evaluate = (result, value) ->
-                message = 'please enter a valid number'
-                if result and value == result
-                    status = 'success'
-                    drmForms.removeNotice('danger', message)
-                else
-                    status = 'danger'
-                    drmForms.issueNotice.call(that, status, message)
-                return status
-
-            if value
-                result = $.trim(re.exec value)
-                status = evaluate(result, value)
-
-            drmForms.applyValidationClass.call(that, status)
-
-        validateURL: ->
-            that = $ @
-            status = null
-            value = $.trim that.val()
-            re = new RegExp('^https?:\\/\\/[\\da-z\\.\\-]+[\\.a-z]{2,6}[\\/\\w/.\\-]*\\/?$','gi')
-
-            evaluate = (result, value) ->
-                message = 'please enter a valid url'
-                if result and value == result
-                    status = 'success'
-                    drmForms.removeNotice('danger', message)
-                else
-                    status = 'danger'
-                    drmForms.issueNotice.call(that, status, message)
-                return status
-
-            if value
-                result = $.trim(re.exec value)
-                status = evaluate(result, value)
-
-            drmForms.applyValidationClass.call(that, status)
-
-        validateEmail: ->
-            that = $ @
-            status = null
-            value = $.trim that.val()
-            re = new RegExp('^[a-z][a-z\\-\\_\\.\\d]*@[a-z\\-\\_\\.\\d]*\\.[a-z]{2,6}$','gi')
-
-            evaluate = (result, value) ->
-                message = 'please enter a valid email address'
-                if result and value == result
-                    status = 'success'
-                    drmForms.removeNotice('danger', message)
-                else
-                    status = 'danger'
-                    drmForms.issueNotice.call(that, status, message)
-                return status
-
-            if value
-                result = $.trim(re.exec value)
-                status = evaluate(result, value)
-
-            drmForms.applyValidationClass.call(that, status)
-
-        validatePhone: ->
-            that = $ @
-            status = null
-            value = $.trim that.val()
-            re = new RegExp('^\\(?\\d{3}[\\)\\-\\.]?\\d{3}[\\-\\.]?\\d{4}(?:[xX]\\d+)?$','gi')
-
-            evaluate = (result, value) ->
-                message = 'please enter a valid phone number'
-                if result and value == result
-                    status = 'success'
-                    drmForms.removeNotice('danger', message)
-                else
-                    status = 'danger'
-                    drmForms.issueNotice.call(that, status, message)
-                return status
-
-            if value
-                result = $.trim(re.exec value)
-                status = evaluate(result, value)
-
-            drmForms.applyValidationClass.call(that, status)
-
-        validateFullName: ->
-            that = $ @
-            status = null
-            value = $.trim that.val()
-            re = new RegExp('^[a-z]+ [a-z\\.\\- ]+$','gi')
-
-            evaluate = (result, value) ->
-                message = 'please enter your first and last name'
-                if result and value == result
-                    status = 'success'
-                    drmForms.removeNotice('danger', message)
-                else
-                    status = 'danger'
-                    drmForms.issueNotice.call(that, status, message)
-                return status
-
-            if value
-                result = $.trim(re.exec value)
-                status = evaluate(result, value)
-
-            drmForms.applyValidationClass.call(that, status)
-
-        validateAlpha: ->
-            that = $ @
-            status = null
-            value = $.trim that.val()
-            re = new RegExp('^[a-z ]*','gi')
-
-            evaluate = (result, value) ->
-                message = 'please use alpha characters only'
-                if result and value == result
-                    status = 'success'
-                    drmForms.removeNotice('danger', message)
-                else
-                    status = 'danger'
-                    drmForms.issueNotice.call(that, status, message)
-                return status
-
-            if value
-                result = $.trim(re.exec value)
-                status = evaluate(result, value)
-
-            drmForms.applyValidationClass.call(that, status)
-
-        validateAlphaNum: ->
-            that = $ @
-            status = null
-            value = $.trim that.val()
-            re = new RegExp('^[a-z\\d ]*$','gi')
-
-            evaluate = (result, value) ->
-                message = 'please use alphanumeric characters only'
-                if result and value == result
-                    status = 'success'
-                    drmForms.removeNotice('danger', message)
-                else
-                    status = 'danger'
-                    drmForms.issueNotice.call(that, status, message)
-                return status
-
-            if value
-                result = $.trim(re.exec value)
-                status = evaluate(result, value)
-
-            drmForms.applyValidationClass.call(that, status)
-
-        validateNoSpaces: ->
-            that = $ @
-            status = null
-            value = $.trim that.val()
-            re = new RegExp('^\\S*$','gi')
-
-            evaluate = (result, value) ->
-                message = 'no spaces'
-                if result and value == result
-                    status = 'success'
-                    drmForms.removeNotice('danger', message)
-                else
-                    status = 'danger'
-                    drmForms.issueNotice.call(that, status, message)
-                return status
-
-            if value
-                result = $.trim(re.exec value)
-                status = evaluate(result, value)
-
-            drmForms.applyValidationClass.call(that, status)
-
-        validateAlphaNumDash: ->
-            that = $ @
-            status = null
-            value = $.trim that.val()
-            re = new RegExp('^[a-z\\d- ]*$','gi')
-
-            evaluate = (result, value) ->
-                message = 'please use alphanumeric and dashes only'
-                if result and value == result
-                    status = 'success'
-                    drmForms.removeNotice('danger', message)
-                else
-                    status = 'danger'
-                    drmForms.issueNotice.call(that, status, message)
-                return status
-
-            if value
-                result = $.trim(re.exec value)
-                status = evaluate(result, value)
-
-            drmForms.applyValidationClass.call(that, status)
-
-        validateAlphaNumUnderscore: ->
-            that = $ @
-            status = null
-            value = $.trim that.val()
-            # allows alphanumeric characters and underscores; no spaces
-            re = new RegExp('^[a-z\\d_]*$','gi')
-
-            evaluate = (result, value) ->
-                message = 'please use alphanumeric and underscores only. no spaces'
-                if result and value == result
-                    status = 'success'
-                    drmForms.removeNotice('danger', message)
-                else
-                    status = 'danger'
-                    drmForms.issueNotice.call(that, status, message)
-                return status
-
-            if value
-                result = $.trim(re.exec value)
-                status = evaluate(result, value)
-
-            drmForms.applyValidationClass.call(that, status)
-
-        validateNoTags: ->
-            that = $ @
-            status = null
-            value = $.trim that.val()
-            re = new RegExp('<[a-z]+.*>.*<\/[a-z]+>','i')
-
-            evaluate = (result, value) ->
-                message = 'no html tags allowed'
+                if result and value is result
+                    validate.status = 'success'
+                else
+                    validate.message = 'please enter a valid number'
+                    validate.status = 'danger'
+                validate.status
+
+            if value?
+                result = $.trim re.exec value
+                validate.status = evaluate result, value
+
+            validate
+
+        validateURL: (value) ->
+            validate =
+                status: null
+                message: null
+                issuer: 'url'
+            re = new RegExp '^https?:\\/\\/[\\da-z\\.\\-]+[\\.a-z]{2,6}[\\/\\w/.\\-]*\\/?$','gi'
+
+            evaluate = (result, value) ->
+                if result and value is result
+                    validate.status = 'success'
+                else
+                    validate.status = 'danger'
+                    validate.message = 'please enter a valid url'
+                validate.status
+
+            if value?
+                result = $.trim re.exec value
+                validate.status = evaluate result, value
+
+            validate
+
+        validateEmail: (value) ->
+            validate =
+                status: null
+                message: null
+                issuer: 'email'
+            re = new RegExp '^[a-z][a-z\\-\\_\\.\\d]*@[a-z\\-\\_\\.\\d]*\\.[a-z]{2,6}$','gi'
+
+            evaluate = (result, value) ->
+                if result and value is result
+                    validate.status = 'success'
+                else
+                    validate.status = 'danger'
+                    validate.message = 'please enter a valid email address'
+                validate.status
+
+            if value?
+                result = $.trim re.exec value
+                validate.status = evaluate result, value
+
+            validate
+
+        validatePhone: (value) ->
+            validate =
+                status: null
+                message: null
+                issuer: 'phone'
+            # validates United States phone number patterns
+            re = new RegExp '^\\(?\\d{3}[\\)\\-\\.]?\\d{3}[\\-\\.]?\\d{4}(?:[xX]\\d+)?$','gi'
+
+            evaluate = (result, value) ->
+                if result and value is result
+                    validate.status = 'success'
+                else
+                    validate.status = 'danger'
+                    validate.message = 'please enter a valid phone number'
+                validate.status
+
+            if value?
+                result = $.trim re.exec value
+                validate.status = evaluate result, value
+
+            validate
+
+        validateFullName: (value) ->
+            validate =
+                status: null
+                message: null
+                issuer: 'fullName'
+            # allows alpha . -
+            re = new RegExp '^[a-z]+ [a-z\\.\\- ]+$','gi'
+
+            evaluate = (result, value) ->
+                if result and value is result
+                    validate.status = 'success'
+                else
+                    validate.status = 'danger'
+                    validate.message = 'please enter your first and last name'
+                validate.status
+
+            if value?
+                result = $.trim re.exec value
+                validate.status = evaluate result, value
+
+            validate
+
+        validateAlpha: (value) ->
+            validate =
+                status: null
+                message: null
+                issuer: 'alpha'
+            re = new RegExp '^[a-z ]*','gi'
+
+            evaluate = (result, value) ->
+                if result and value is result
+                    validate.status = 'success'
+                else
+                    validate.status = 'danger'
+                    validate.message = 'please use alpha characters only'
+                validate.status
+
+            if value?
+                result = $.trim re.exec value
+                validate.status = evaluate result, value
+
+            validate
+
+        validateAlphaNum: (value) ->
+            validate =
+                status: null
+                message: null
+                issuer: 'alphanum'
+            re = new RegExp '^[a-z\\d ]*$','gi'
+
+            evaluate = (result, value) ->
+                if result and value is result
+                    validate.status = 'success'
+                else
+                    validate.status = 'danger'
+                    validate.message = 'please use alphanumeric characters only'
+                validate.status
+
+            if value?
+                result = $.trim re.exec value
+                validate.status = evaluate result, value
+
+            validate
+
+        validateNoSpaces: (value) ->
+            validate =
+                status: null
+                message: null
+                issuer: 'noSpaces'
+            re = new RegExp '^\\S*$','gi'
+
+            evaluate = (result, value) ->
+                if result and value is result
+                    validate.status = 'success'
+                else
+                    validate.status = 'danger'
+                    validate.message = 'no spaces'
+                validate.status
+
+            if value?
+                result = $.trim re.exec value
+                validate.status = evaluate result, value
+
+            validate
+
+        validateAlphaNumDash: (value) ->
+            validate =
+                status: null
+                message: null
+                issuer: 'alphaNumDash'
+            re = new RegExp '^[a-z\\d- ]*$','gi'
+
+            evaluate = (result, value) ->
+                if result and value is result
+                    validate.status = 'success'
+                else
+                    validate.status = 'danger'
+                    validate.message = 'please use alphanumeric and dash characters only'
+                validate.status
+
+            if value?
+                result = $.trim re.exec value
+                validate.status = evaluate result, value
+
+            validate
+
+        validateAlphaNumUnderscore: (value) ->
+            validate =
+                status: null
+                message: null
+                issuer: 'alphaNumUnderscore'
+            # allows alphanumeric characters and underscores; no spaces; recommended for usernames
+            re = new RegExp '^[a-z\\d_]*$','gi'
+
+            evaluate = (result, value) ->
+                if result and value is result
+                    validate.status = 'success'
+                else
+                    validate.status = 'danger'
+                    validate.message = 'please use alphanumeric and underscores only. no spaces'
+                validate.status
+
+            if value?
+                result = $.trim re.exec value
+                validate.status = evaluate result, value
+
+            validate
+
+        validateNoTags: (value) ->
+            validate =
+                status: null
+                message: null
+                issuer: 'number'
+            re = new RegExp '<[a-z]+.*>.*<\/[a-z]+>','i'
+
+            evaluate = (result) ->
                 if result
-                    status = 'danger'
-                    drmForms.issueNotice.call(that, status, message)
-                else
-                    status = 'success'
-                    drmForms.removeNotice('danger', message)
-                return status
-
-            if value
-                result = $.trim(re.exec value)
-                status = evaluate(result, value)
-
-            drmForms.applyValidationClass.call(that, status)
-
-        validateMaxValue: ->
+                    validate.status = 'danger'
+                    validate.message = 'no html tags allowed'
+                else
+                    validate.status = 'success'
+                validate.status
+
+            if value?
+                result = $.trim re.exec value
+                validate.status = evaluate result, value
+
+            validate
+
+        validateMaxValue: (value) ->
             that = $ @
             max = that.data 'max-value'
             value = $.trim that.val()

             evaluate = (max, value) ->
-                message = "please enter a value that is less than #{max + 1}"
                 if value > max
-                    status = 'danger'
-                    drmForms.issueNotice.call(that, status, message)
-                else
-                    status = 'success'
-                    drmForms.removeNotice('danger', message)
-                return status
-
-            if value
-                status = evaluate(max, value)
-
-            drmForms.applyValidationClass.call(that, status)
-
-        validateMinValue: ->
+                    validate.status = 'danger'
+                    validate.message = "please enter a value that is less than #{max + 1}"
+                else
+                    validate.status = 'success'
+                validate.status
+
+            if value?
+                validate.status = evaluate max, value
+
+            validate
+
+        validateMinValue: (value) ->
             that = $ @
             min = that.data 'min-value'
             value = $.trim that.val()

-            evaluate = (min, value) ->
-                message = "please enter a value of at least #{min}"
+            evaluate = (min, value) ->
                 if value < min
-                    status = 'danger'
-                    drmForms.issueNotice.call(that, status, message)
-                else
-                    status = 'success'
-                    drmForms.removeNotice('danger', message)
-                return status
-
-            if value
-                status = evaluate(min, value)
-
-            drmForms.applyValidationClass.call(that, status)
-
-        validateBetweenValue: ->
+                    validate.status = 'danger'
+                    validate.message = "please enter a value of at least #{min}"
+                else
+                    validate.status = 'success'
+                validate.status
+
+            if value?
+                validate.status = evaluate(min, value)
+
+            validate
+
+        validateBetweenValue: (value) ->
             that = $ @
             min = that.data 'min-value'
             max = that.data 'max-value'
             value = $.trim that.val()

-            evaluate = (min, max, value) ->
-                message = "please enter a value that is between #{min - 1} and #{max + 1}"
+            evaluate = (min, max, value) ->
                 if (value < min) or (value > max)
-                    status = 'danger'
-                    drmForms.issueNotice.call(that, status, message)
-                else
-                    status = 'success'
-                    drmForms.removeNotice('danger', message)
-                return status
-
-            if value
-                status = evaluate(min, max, value)
-
-            drmForms.applyValidationClass.call(that, status)
-
-        validateMaxLength: ->
+                    validate.status = 'danger'
+                    validate.message = "please enter a value that is between #{min - 1} and #{max + 1}"
+                else
+                    validate.status = 'success'
+                validate.status
+
+            if value?
+                validate.status = evaluate min, max, value
+
+            validate
+
+        validateMaxLength: (value) ->
             that = $ @
             max = that.data 'max-length'
             value = $.trim that.val()
             length = value.length

-            evaluate = (max, length) ->
-                message = "please enter less than #{max + 1} characters"
+            evaluate = (max, length) ->
                 if length > max
-                    status = 'danger'
-                    drmForms.issueNotice.call(that, status, message)
-                else
-                    status = 'success'
-                    drmForms.removeNotice('danger', message)
-                return status
-
-            if value
-                status = evaluate(max, length)
-
-            drmForms.applyValidationClass.call(that, status)
-
-        validateMinLength: ->
+                    validate.status = 'danger'
+                    validate.message = "please enter less than #{max + 1} characters"
+                else
+                    validate.status = 'success'
+                validate.status
+
+            if value?
+                validate.status = evaluate max, length
+
+            validate
+
+        validateMinLength: (value) ->
             that = $ @
             min = that.data 'min-length'
             value = $.trim that.val()
             length = value.length

-            evaluate = (min, length) ->
-                message = "please enter at least #{min} characters"
+            evaluate = (min, length) ->
                 if length < min
-                    status = 'danger'
-                    drmForms.issueNotice.call(that, status, message)
-                else
-                    status = 'success'
-                    drmForms.removeNotice('danger', message)
-                return status
-
-            if value
-                status = evaluate(min, length)
-
-            drmForms.applyValidationClass.call(that, status)
-
-        validateBetweenLength: ->
+                    validate.status = 'danger'
+                    validate.message = "please enter at least #{min} characters"
+                else
+                    validate.status = 'success'
+                validate.status
+
+            if value?
+                validate.status = evaluate min, length
+
+            validate
+
+        validateBetweenLength: (value) ->
             that = $ @
             min = that.data 'min-length'
             max = that.data 'max-length'
             value = $.trim that.val()
             length = value.length

-            evaluate = (min, max, length) ->
-                message = "please enter a value that is between #{min - 1} and #{max + 1} characters"
+            evaluate = (min, max, length) ->
                 if (length < min) or (length > max)
-                    status = 'danger'
-                    drmForms.issueNotice.call(that, status, message)
-                else
-                    status = 'success'
-                    drmForms.removeNotice('danger', message)
-                return status
-
-            if value
-                status = evaluate(min, max, length)
-
-            drmForms.applyValidationClass.call(that, status)
-    }
-
-    drmForms.init()
+                    validate.status = 'danger'
+                    validate.message = "please enter a value that is between #{min - 1} and #{max + 1} characters"
+                else
+                    validate.status = 'success'
+                validate.status
+
+            if value?
+                validate.status = evaluate min, max, length
+
+            validate
+
+    new DrmForms()

 ) jQuery


Fade the trash can icon

Fade the trash can icon

--- app/controllers/lists.title.coffee
+++ app/controllers/lists.title.coffee
@@ -24,10 +24,10 @@
     # Disables contenteditable on noneditable lists
     if @list.permanent
       @listName.removeAttr("contenteditable")
-      @deleteButton.hide()
+      @deleteButton.fadeOut(300)
     else
       @listName.attr("contenteditable", true)
-      @deleteButton.show()
+      @deleteButton.fadeIn(300)

   # This is fired on keyup when a list is renamed
   rename: (e) ->


Refactor ElasticSearch, add searchSongarySong.

--- app/server/js/elasticsearch.coffee
+++ app/server/js/elasticsearch.coffee
@@ -26,10 +26,17 @@
       response.hits.hits.map (hit) -> hit._source

   ###*
+    @param {Object} body
+    @return {goog.Promise}
+  ###
+  searchSongarySong: (body) ->
+    @search index: 'songary', type: 'song', body: body
+
+  ###*
     @return {goog.Promise}
   ###
   getRecentlyUpdatedSongs: ->
-    @asSource @search index: 'songary', type: 'song', body:
+    @asSource @searchSongarySong
       sort: updatedAt: order: 'desc'

   ###*
@@ -38,7 +45,7 @@
     @return {goog.Promise}
   ###
   getSongsByUrl: (urlArtist, urlName) ->
-    @asSource @search index: 'songary', type: 'song', body:
+    @asSource @searchSongarySong
       query: filtered: filter: bool: must: [
         term: urlName: urlName
       ,
@@ -50,8 +57,7 @@
     @return {goog.Promise}
   ###
   searchSongsByQuery: (query) ->
-    # TODO: Ignore diacritics, boost name and artist, use detected language.
-    @asSource @search index: 'songary', type: 'song', body:
+    @asSource @searchSongarySong
       query: bool: should: [
         match: name: query: query, operator: 'and'
       ,


Fix: Inflector now doesn't force convert to lower case.


tweaked movement

tweaked movement

--- src/lib/Character/Player/Player.coffee
+++ src/lib/Character/Player/Player.coffee
@@ -73,8 +73,8 @@
         dy = if @controls.up.isDown or @controls.W.isDown then dy - 1 else dy

         # adjust acceleration if controls used
-        @sprite.body.acceleration.x = if @sprite.body.velocity.x > 0 then dx*300 else dx*150
-        @sprite.body.acceleration.y = if @sprite.body.velocity.y > 0 then dy*300 else dy*150
+        @sprite.body.acceleration.x = if @sprite.body.velocity.x * dx < 0 then dx*300 else dx*150
+        @sprite.body.acceleration.y = if @sprite.body.velocity.y * dy < 0 then dy*300 else dy*150

         # drag if no controls in use
         @sprite.body.acceleration.x = -5 * @sprite.body.velocity.x if @sprite.body.acceleration.x == 0


Call the correct progress bar cleanup method

Fixes #3718

--- admin/static/coffee/tables/table.coffee
+++ admin/static/coffee/tables/table.coffee
@@ -882,5 +882,5 @@
             @$('.alert_confirm_delete').hide()

         remove: =>
-            @progress_bar?.destroy()
+            @progress_bar?.remove()
             super()


Utils: Added applyGradient method.

Utils: Added applyGradient method.

--- client/Framework/core/utils.coffee
+++ client/Framework/core/utils.coffee
@@ -686,6 +686,14 @@
         i += 3
     string

+  applyGradient: (kdViewInstance, color1, color2) ->
+    rules = [
+      "-moz-linear-gradient(100% 100% 90deg, #{color2}, #{color1})"
+      "-webkit-gradient(linear, 0% 0%, 0% 100%, from(#{color1}), to(#{color2}))"
+      "-o-linear-gradient(#{color1}, #{color2})"
+    ]
+    kdViewInstance.setCss "backgroundImage", rule for rule in rules
+
   # Return true x% of time based on argument.
   #
   # Example:


Terminal: "session not found" error and actual terminal connection failures are defined as distinctive errors

--- client/Terminal/webtermview.coffee
+++ client/Terminal/webtermview.coffee
@@ -131,9 +131,15 @@
     @status = null
     @on "healthCheckCompleted", =>
       @status = healthChecker.status
-      if @status is "fail"
-        @terminal.cursor.setFocused false
-        @terminal.cursor.stopBlink()
+      switch @status
+        when "fail"
+          @setBackoffTimeout(
+            @bound "attemptToReconnect"
+            @bound "handleConnectionFailure"
+          )
+        when "session not found"
+          @terminal.cursor.setFocused false
+          @terminal.cursor.stopBlink()

     @checker = KD.utils.repeat 15000, ->
       healthChecker.run()
@@ -141,8 +147,10 @@
   healthCheck: (callback) ->
     kite = KD.getSingleton("vmController").terminalKites[@getVMName()]
     {session} = @getOptions()
-    kite.webtermGetSessions().then (sessions) ->
-      callback null  if session in sessions
+    # TODO we need a session check method in terminal kite
+    kite?.webtermGetSessions().then (sessions) ->
+      return callback null  if session in sessions
+      @status = "session not found"
     .catch (err) =>
       KD.utils.killRepeat @checker


router: redirect to /Welome if the team is new

--- client/app/lib/kodingrouter.coffee
+++ client/app/lib/kodingrouter.coffee
@@ -95,17 +95,16 @@
     #
     # This doesn't effect the routes direct accesses. ~ GG

-    if isLoggedIn()
+    if not currentGroup or currentGroup.slug is 'koding'
+      return '/IDE'

-      if not currentGroup or currentGroup.slug is 'koding'
-        return '/IDE'
-
-      if groupsController.currentGroupHasStack()
-      then return '/IDE'
+    if groupsController.currentGroupHasStack()
+    then return '/IDE'
+    else
+      if groupsController.currentGroupIsNew()
+      then return '/Welcome'
       else return '/Activity'

-    else
-      return '/Home'

   setPageTitle: (title = 'Koding') -> kd.singletons.pageTitle.update title


Improved: popover positioning

Improved: popover positioning

--- client/app/lib/popover_view.coffee
+++ client/app/lib/popover_view.coffee
@@ -124,21 +124,26 @@
         # Define everything that will be needed for positionning.
         popoverWidth = @$popover.innerWidth()

+        # Container is the screen, except the sidebar.
+        containerOffset = @container.offset()
+        containerHeight = @container.innerHeight()
+        containerWidth = @container.innerWidth()
+
         # Target is the calendar's cell clicked.
         targetOffset = @target.offset()
         targetWidth = @target.width()
+        targetLeftBorder = targetOffset.left - containerOffset.left

-        # Container is the screen, except the sidebar.
-        containerHeight = @container.innerHeight()
-        containerWidth = @container.innerWidth()
+        # Margin between the popover and the cell.
+        popoverMargin = 15

         # Cell is on the left side
         if targetOffset.left < (containerWidth / 2)
-            left = targetOffset.left + 50
+            left = targetLeftBorder + targetWidth + popoverMargin

         # Cell is on the right side
         else
-            left = targetOffset.left - popoverWidth - targetWidth + 25
+            left = targetLeftBorder - popoverWidth - popoverMargin

         # Cell is on the upper side
         if targetOffset.top < (containerHeight / 2)


fixing sticky element calculation

fixing sticky element calculation

Former-commit-id: 0eb438aff1fe71ad2e5063e02aac96d61ba8b83c [formerly 7d6d2c1e4e67b62c82ea27a94f866a56808e2767] [formerly ad4704f688b125e77d465acd149d6dac69b96f5a [formerly a2da133076a47c68093f4e3570f5c7a23fa42bdd]] [formerly 6f6c702c4d5ba34bcf7879632a17a7bd9b89c62a [formerly 82e5eeb888f6f224383b04fb1f3fdc626e463b77] [formerly b814d3e46af52e3f43944cd1ccc284c4b34c0b21 [formerly b814d3e46af52e3f43944cd1ccc284c4b34c0b21 [formerly 27a0181734c924dec20b26cf751bf9968afe6026]]]]
Former-commit-id: 520cdebfbe7dbf089edf248285a74a9e0fb4a3b8 [formerly fd7b038e14db047c37c4b4a0f467501d51971fec] [formerly a94867b27b615a77d1a03a278b00e6f54cb328f4 [formerly 58f4be8b89bfead874324af6a44100e7d952be1d]]
Former-commit-id: 7425023caf0bb7afa44003cf407aad4e2e09ee10 [formerly a650a7e19b70628b9b795f5379993b52a96b8f0d]
Former-commit-id: 80b2ab0b99b5bedae5820d6d2ad3ff7a246129f9
Former-commit-id: c2e1a56f5a73b88cb278968c679ffdd8698bef42

--- @client/sticky_element.coffee
+++ @client/sticky_element.coffee
@@ -138,17 +138,17 @@
       # pinch zoom and scroll left. Under certain conditions, we have to do a complex
       # calculation to figure out how much to offset the fixed element.

-      # If the full screen is visible, scroll_x will be 0; if the window is small, but no pinch zooming,
-      # it will be $(window).scrollLeft(). Otherwise...
+      scroll_x = $(window).scrollLeft()
+
       # If the user has pinch zoomed when the window is smaller than the document,
       # we can't just use scrollLeft directly. Instead, we need to adjust scrollLeft
       # based on the difference between the actual window width, the width of the zoomed
       # area the user sees, and the % amount of horizontal scrolling they've done.
       zoom_width_difference = $(window).width() - window.innerWidth
-      max_scroll_left =       $(document).width() - window.innerWidth
-      zoom_adjustment =       $(window).scrollLeft() / max_scroll_left
-
-      scroll_x = $(window).scrollLeft() - zoom_adjustment * zoom_width_difference
+      if zoom_width_difference != 0
+        max_scroll_left =       $(document).width() - window.innerWidth
+        zoom_adjustment =       $(window).scrollLeft() / max_scroll_left
+        scroll_x -= zoom_adjustment * zoom_width_difference if zoom_width_difference > 0

       # (temporarily addressed) BUG:
       # The scroll_x adjustment above does not immediately work on iOS devices.


Style correction.

Style correction.

--- src/code/utils/codap-connect.coffee
+++ src/code/utils/codap-connect.coffee
@@ -11,7 +11,7 @@
   init: (linkManager, name) ->
     log.info 'CodapConnect initializing'
     @linkManager = linkManager
-    name && @name = name
+    name and @name = name

     @codapPhone = new IframePhoneRpcEndpoint( @codapRequestHandler,
       'codap-game', window.parent )


Fix Dropdown's item keys

Fix Dropdown's item keys

As mentioned by @knowuh, all dynamic children need to have a key
assigned. This fixes the previous removal of `key`.

Note that this commit removes the key definition in the child view
itself, because that's apparently not necessary (see
https://facebook.github.io/react/docs/multiple-components.html#dynamic-children)

--- src/code/views/dropdown-view.coffee
+++ src/code/views/dropdown-view.coffee
@@ -9,7 +9,8 @@

   render: ->
     className = "menuItem #{if @props.isActionMenu and not @props.item.action then 'disabled' else ''}"
-    (li {key: @props.name, className: className, onClick: @clicked }, @props.name)
+    name = @props.item.name or @props.item
+    (li {className: className, onClick: @clicked }, name)

 module.exports = DropDown = React.createClass

@@ -54,7 +55,7 @@
       )
       (div {className: menuClass, onMouseLeave: @blur, onMouseEnter: @unblur},
         (ul {},
-          (DropdownItem {name: item.name or item, item: item, select: @select, isActionMenu: @props.isActionMenu}) for item in @props.items
+          (DropdownItem {key: item.name or item, item: item, select: @select, isActionMenu: @props.isActionMenu}) for item in @props.items
         )
       )
     )


Fix workflow in case of no address.

Fix workflow in case of no address.

--- src/coffee/customerxmlimport.coffee
+++ src/coffee/customerxmlimport.coffee
@@ -58,16 +58,17 @@
         paymentInfo = data.paymentInfo
         if _.has email2id, customer.email
           if _.size(email2id[customer.email].addresses or []) is 0
-            console.warn "Customer without address"
+            Q "Customer without address"
             #@addAddress(email2id[customer.email], customer.addresses[0])
-          #if email2id[customer.email].customerGroup?
-          #  @linkCustomerIntoGroup(email2id[customer.email], customer.customerGroup)
-
-          Q "Update of customer is not implemented yet - email '#{customer.email}' exist!"
-
-          #@resetPassword customer, customer.email, email2id[customer.email]
-          #@ensurePaymentInfo email2id[customer.email], paymentInfo
-          #@syncIdenfifier customer, email2id[customer.email]
+          #if not email2id[customer.email].customerGroup?
+            #@linkCustomerIntoGroup(email2id[customer.email], customer.customerGroup)
+
+          else
+            Q "Update of customer is not implemented yet - email '#{customer.email}' exist!"
+
+            #@resetPassword customer, customer.email, email2id[customer.email]
+            #@ensurePaymentInfo email2id[customer.email], paymentInfo
+            #@syncIdenfifier customer, email2id[customer.email]
         else if _.contains(usedCustomerNumbers, customer.customerNumber)
           Q "Update of customer is not implemented yet - number '#{customer.customerNumber}' exists!"
         else
@@ -75,9 +76,9 @@

       if _.size(posts) is 0
         Q 'Nothing done.'
-
-      console.log "Processing #{_.size posts} customer(s)..."
-      Q.all posts
+      else
+        console.log "Processing #{_.size posts} customer(s)..."
+        Q.all posts

   resetPassword: (newCustomer, email, existingCustomer) ->
     @client.customers._task.addTask =>


melhora comunicacao com eventos

melhora comunicacao com eventos

--- src/coffee/portal-totalizers.coffee
+++ src/coffee/portal-totalizers.coffee
@@ -2,7 +2,7 @@

   pluginName = 'vtexTotalizers'
   defaults = {
-    template: null
+    $template: null
   }

   class vtexTotalizers
@@ -49,8 +49,11 @@

       self.getCartData()

-      $(window).on 'cartUpdated', ->
-        self.getCartData()
+      $(window).on 'cartUpdated', (event, cartData) ->
+        if (cartData)
+          self.setCartData(cartData)
+        else
+          self.getCartData()

       $('.amount-items-in-cart, .show-minicart-on-hover').mouseover ->
         $(window).trigger 'miniCartMouseOver'
@@ -65,7 +68,7 @@
         num = value / 100
       parseFloat(num).toFixed(2).replace('.', ',').toString().replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1.')

-    getCartData: (items) ->
+    getCartData: ->
       self = this

       $(self.options.$template).addClass 'amount-items-in-cart-loading'
@@ -82,20 +85,25 @@
         $(self.options.$template).removeClass 'amount-items-in-cart-loading'

       promise.success (data) ->
-        amountProducts = data.items.length
-        amountItems = 0;
-        amountItems += item.quantity for item in data.items
-        totalCart = self.formatCurrency data.value
-
-        self.selectors.amountProducts.html amountProducts
-        self.selectors.amountItems.html amountItems
-        self.selectors.totalCart.html totalCart
+        self.setCartData data

       promise.fail (jqXHR, textStatus, errorThrown) ->
         console.log 'Error Message: ' + textStatus;
         console.log 'HTTP Error: ' + errorThrown;

       promise
+
+    setCartData: (data) ->
+      self = this
+
+      amountProducts = data.items.length
+      amountItems = 0;
+      amountItems += item.quantity for item in data.items
+      totalCart = self.formatCurrency data.value
+
+      self.selectors.amountProducts.html amountProducts
+      self.selectors.amountItems.html amountItems
+      self.selectors.totalCart.html totalCart

     $.fn[pluginName] = (options) ->
       @each ->
@@ -104,5 +112,5 @@

 )( jQuery, window, document )

-$ -> $('.portal-totalizers-ref').vtexTotalizers()
-# $ -> $('.carrinhoCompras a').vtexTotalizers()
+# $ -> $('.portal-totalizers-ref').vtexTotalizers()
+$ -> $('.carrinhoCompras a').vtexTotalizers()


Add tests for default-separator warnings

--- spec/list-edit-spec.coffee
+++ spec/list-edit-spec.coffee
@@ -138,6 +138,34 @@
       atom.commands.dispatch editorView, 'list-edit:paste'
       expect(editor.getText()).toBe '[   Clyde   ]'

+  describe 'separator defaulting', ->
+    beforeEach ->
+      spyOn(atom.notifications, 'addWarning')
+
+    it 'warns when a default separator is used', ->
+      listCopyFrom '[NoSep]', 0, 3
+      editor.setText '{NoSepEither}'
+      editor.setCursorBufferPosition [0, 1]
+      atom.commands.dispatch editorView, 'list-edit:paste'
+      expect(editor.getText()).toBe '{NoSep; NoSepEither}'
+      expect(atom.notifications.addWarning).toHaveBeenCalledWith('Separator unknown, using default: \';\'');
+
+    it 'does not warn when a default separator isn\'t used because target range wasn\'t empty', ->
+      listCopyFrom '[NoSep]', 0, 3
+      editor.setText '{one, two, three}'
+      editor.setCursorBufferPosition [0, 7] # paste on two
+      atom.commands.dispatch editorView, 'list-edit:paste'
+      expect(editor.getText()).toBe '{one, NoSep, three}'
+      expect(atom.notifications.addWarning).not.toHaveBeenCalled();
+
+    it 'does not warn when a default separator isn\'t used because target list was empty', ->
+      listCopyFrom '[NoSep]', 0, 3
+      editor.setText '{}'
+      editor.setCursorBufferPosition [0, 1] # paste in empty list
+      atom.commands.dispatch editorView, 'list-edit:paste'
+      expect(editor.getText()).toBe '{NoSep}'
+      expect(atom.notifications.addWarning).not.toHaveBeenCalled();
+
 describe 'ListEdit on a file with a JavaScript grammar', ->
   [editor, editorView] = []
   beforeEach ->


Change CLSI proxy errors to warnings

Change CLSI proxy errors to warnings

--- services/web/app/coffee/Features/Compile/CompileController.coffee
+++ services/web/app/coffee/Features/Compile/CompileController.coffee
@@ -42,5 +42,5 @@
 		proxy = request(url: url, method: req.method, timeout: oneMinute)
 		proxy.pipe(res)
 		proxy.on "error", (error) ->
-			logger.error err: error, url: url, "CLSI proxy error"
+			logger.warn err: error, url: url, "CLSI proxy error"


Increase autocompile rollout to 20%

Increase autocompile rollout to 20%

--- services/web/app/coffee/Features/Project/ProjectController.coffee
+++ services/web/app/coffee/Features/Project/ProjectController.coffee
@@ -259,7 +259,7 @@
 				timestamp = parseInt(user_id.toString().substring(0, 8), 16)
 				counter = parseInt(user_id.toString().substring(18, 24), 16)

-				rolloutPercentage = 10 # Percentage of users to roll out to
+				rolloutPercentage = 20 # Percentage of users to roll out to
 				if counter % 100 > rolloutPercentage
 					# Don't show if user is not part of roll out
 					return cb(null, false)


Clean up comments and callbacks

Clean up comments and callbacks

--- services/web/public/coffee/ide/editor/directives/aceEditor.coffee
+++ services/web/public/coffee/ide/editor/directives/aceEditor.coffee
@@ -109,23 +109,20 @@
 				metadataManager = new MetadataManager(scope, editor, element, metadata)
 				autoCompleteManager = new AutoCompleteManager(scope, editor, element, metadataManager, graphics, preamble, files)

-				# Prevert Ctrl|Cmd-S from triggering save dialog
 				scope.$watch "onSave", (callback) ->
 					if callback?
 						Vim.defineEx 'write', 'w', callback
 						editor.commands.addCommand
 							name: "save",
 							bindKey: win: "Ctrl-S", mac: "Command-S"
-							exec: () ->
-								callback()
+							exec: callback
 							readOnly: true
 						# Not technically 'save', but Ctrl-. recompiles in OL v1
 						# so maintain compatibility
 						editor.commands.addCommand
 							name: "recompile_v1",
 							bindKey: win: "Ctrl-.", mac: "Ctrl-."
-							exec: () ->
-								callback()
+							exec: callback
 							readOnly: true
 				editor.commands.removeCommand "transposeletters"
 				editor.commands.removeCommand "showSettingsMenu"


sankey: changed tooltip position

sankey: changed tooltip position

--- app/assets/javascripts/d3/sankey.coffee
+++ app/assets/javascripts/d3/sankey.coffee
@@ -442,7 +442,7 @@
             text: -> $(this).attr('data-tooltip')
           position:
             target: 'mouse'
-            my: 'bottom right'
+            my: 'right center'
             at: 'top center'

     # callbacks


Use first param for callback when getting

--- src/octonode/me.coffee
+++ src/octonode/me.coffee
@@ -148,8 +148,8 @@
       @createRepo cbOrRepo, cb
     else
       @client.get "/user/repos", (err, s, b) ->
-        return cb(err) if err
-        if s isnt 200 then cb(new Error('User repos error')) else cb null, b
+        return cbOrRepo(err) if err
+        if s isnt 200 then cbOrRepo(new Error('User repos error')) else cbOrRepo null, b

   # Create a repository
   # '/user/repos' POST


Add tooltips back in

Add tooltips back in

--- app/assets/javascripts/groups.js.coffee
+++ app/assets/javascripts/groups.js.coffee
@@ -64,7 +64,17 @@
           Application.convertUtcToRelativeTime()
           $("#discussion-list").removeClass('hidden')
           $("#discussions-loading").addClass('hidden')
-          activate_tool_tips()
+          activate_discussions_tooltips()
         )
         e.preventDefault()
     )
+
+# adds bootstrap popovers to group activity indicators
+activate_discussions_tooltips = () ->
+  $(".unread-group-activity").tooltip
+    placement: "top"
+    title: 'There have been new comments on this discussion since you last visited the group.'
+
+$ ->
+  $("#privacy").tooltip
+    placement: "top"


Improvements on own bindonce directives.

--- app/coffee/modules/base/bindonce.coffee
+++ app/coffee/modules/base/bindonce.coffee
@@ -19,20 +19,30 @@
 # File: modules/base/bindonce.coffee
 ###

+bindOnce = (scope, attr, continuation) =>
+    val = scope.$eval(attr)
+    if val != undefined
+        return continuation(val)
+
+    delBind = null
+    delBind = scope.$watch attr, (val) ->
+        return if val is undefined
+        continuation(val)
+        delBind() if delBind
+
 # Html bind once directive
 BindHtmlDirective = ->
     link = (scope, element, attrs) ->
-        val = scope.$eval(attrs.tgBoHtml)
-        scope.$watch attrs.tgBoHtml, (val) ->
-            element.html(val) if val
+        bindOnce scope, attrs.tgBoHtml, (val) ->
+            element.html(val)

     return {link:link}

 # Object reference bind once helper.
 BindRefDirective = ->
     link = (scope, element, attrs) ->
-        val = scope.$eval(attrs.tgBoRef)
-        element.html("##{val} ")
+        bindOnce scope, attrs.tgBoRef, (val) ->
+            element.html("##{val} ")
     return {link:link}

 module = angular.module("taigaBase")


initial adding of practice button on crude learning guide, need to check with backend on how to trigger api

--- src/components/task-plan/guide.cjsx
+++ src/components/task-plan/guide.cjsx
@@ -5,6 +5,7 @@

 {CourseStore, CourseActions} = require '../../flux/course'
 LoadableMixin = require '../loadable-mixin'
+PracticeButton = require '../practice-button'

 GuideShell = React.createClass
   mixins: [LoadableMixin]
@@ -21,6 +22,7 @@
     courseId

   renderCrudeTable: (data,i) ->
+    courseId = @getId()

       {data.id}
@@ -30,6 +32,7 @@
       {data.current_level}
       {data.page_ids}
       {data.practice_count}
+      <PracticeButton courseId={courseId}/>

@@ -55,6 +58,7 @@
                   current_level
                   page_ids
                   practice_count
+                  actions


Fixed LC.renderSnapshotToSVG

Fixed LC.renderSnapshotToSVG

--- src/core/renderSnapshotToSVG.coffee
+++ src/core/renderSnapshotToSVG.coffee
@@ -29,5 +29,5 @@
       opts.margin
     )

-  return LC.renderShapesToSVG(
+  return util.renderShapesToSVG(
     backgroundShapes.concat(shapes), opts.rect, colors.background)


show better error if name is already registered

--- app/js/controllers/createaccount.coffee
+++ app/js/controllers/createaccount.coffee
@@ -8,10 +8,6 @@
         Wallet.create_account(name, {'gui_data': {'website': $scope.website}}).then (pubkey)=>
             $location.path("accounts/" + name)
         , (response) ->
-            if response.data.error.code == 10 and response.data.error.message
-                message = response.data.error.message.replace(/(\r\n|\n|\r)/gm,'')
-                regex_match = message.match(/Assert\sException.+\:\s?(.+)/i)
-                if regex_match and regex_match.length > 1
-                    form.account_name.error_message = regex_match[1]
-                else
-                    form.account_name.error_message = message+            console.log response
+            if response.data.error
+                form.account_name.error_message = response.data.error.message


Typo fix

Typo fix

--- src/lib/Network.coffee
+++ src/lib/Network.coffee
@@ -261,7 +261,7 @@
         to: newId
       do processOps unless processing
     @graph.on 'addEdge', (edge) =>
-      registerOp 'renameNode', oldId, newId
+      registerOp 'addEdge', edge
       do processOps unless processing
     @graph.on 'removeEdge', (edge) =>
       registerOp 'removeEdge', edge


fixed bug where alchemy.conf.edgeTypes is null and filtering would throw and error

--- app/scripts/alchemy/core/filters.coffee
+++ app/scripts/alchemy/core/filters.coffee
@@ -185,7 +185,7 @@

                     for edge in alchemy.edge.filter("[source-target*='#{nodeId}']").data()
                         edgeType = edge.caption
-                        if edgeType in alchemy.conf.edgeTypes
+                        if alchemy.conf.edgeTypes? and edgeType in alchemy.conf.edgeTypes
                             #the edge should not show if target of source node is inactive
                             if d3.select("#li-#{edgeType}").classed("disabled")
                                 alchemy.edge.filter("[source-target*='#{nodeId}']")


feat(home): update UI at the end of any action by calling `$scope.$apply()`

--- app/scripts/controllers/HomeCtrl.coffee
+++ app/scripts/controllers/HomeCtrl.coffee
@@ -50,6 +50,7 @@
 		$scope.syncDown = ( callback = -> ) ->
 			unless $scope.isSyncing
 				$scope.isSyncing = yes
+				$scope.$apply()

 				downloadRecursive = ->
 					next = audio.getFirst "remoteOnly"
@@ -57,6 +58,7 @@
 						$scope.download next, downloadRecursive
 					else
 						$scope.isSyncing = no
+						$scope.$apply()
 						callback()

 				removeLocalRecursive = ->
@@ -72,6 +74,7 @@
 		$scope.syncUp = ( callback = -> ) ->
 			unless $scope.isSyncing
 				$scope.isSyncing = yes
+				$scope.$apply()

 				uploadRecursive = ->
 					next = audio.getFirst "localOnly"
@@ -79,6 +82,7 @@
 						$scope.upload next, uploadRecursive
 					else
 						$scope.isSyncing = no
+						$scope.$apply()
 						callback()

 				removeRemoteRecursive = ->


Show add bar when clicking add button right after adding a domain (fix #31)

--- app/scripts/directives/headerbar.coffee
+++ app/scripts/directives/headerbar.coffee
@@ -32,7 +32,7 @@
           YUpperThreshold = Math.abs($(YThresElem).position().top) - $($window).scrollTop()
           YLowerThreshold = Math.abs($(YThresElem).position().top) + $(YThresElem).height() - $($window).scrollTop()
           Xthreshold = Math.abs($(XThresElem).position().left)
-          if ev.y > YLowerThreshold or ev.y < YUpperThreshold and ev.x < Xthreshold
+          if ev.clientY > YLowerThreshold or ev.clientY < YUpperThreshold and ev.clientX < Xthreshold
             scope.model[obj] = false
             scope.hideAlertMessage()
             scope.$apply()
@@ -44,12 +44,14 @@
           _hideBar(ev, '.header-add-page', '.search-form', 'searchFormVisible')

       scope.toggleAddPage = ->
+        if scope.model.alertMessageVisible and scope.model.addPageFormVisible
+          _toggle('alertMessageVisible')
         _toggle('addPageFormVisible')

       scope.toggleSearch = ->
         _toggle('searchFormVisible')

       scope.hideAlertMessage = (headerWitchOne) ->
+        scope.model.alertMessageVisible = false
         if headerWitchOne == 'success_page'
           scope.toggleAddPage()
-        scope.model.alertMessageVisible = false