- cleanup whitespace

- cleanup whitespace

--- kites/terminal/api.coffee
+++ kites/terminal/api.coffee
@@ -2,14 +2,12 @@
 Kite            = require 'kite'
 _               = require 'underscore'
 dmp             = new (new require('diff_match_patch')).diff_match_patch()
-{FakeTerminal,FakeController} = require './faketerminal'
-
+# {FakeTerminal,FakeController} = require './faketerminal'

 {Terminal}  = require("terminaljs").Terminal
 {htmlify}   = require("terminaljs")

 console.log "my pid is:",process.pid
-

 module.exports = new Kite 'terminaljs'

@@ -20,7 +18,7 @@
   create  : (options,callback)  =>
     console.log "creating new terminal for #{options.username}"
     {username,rows,cols,callbacks} = options
-
+
     unless username and rows and cols and callbacks
       console.log "invalid options, usage : create({rows,cols,type,callbacks},callback)"
     else
@@ -34,22 +32,22 @@
       terminal.on "data",_.throttle (screen)->
         # scr = ( screen.row(line) for line in [0..screen.rows]).join "\n"
         scr = htmlify.convert screen
-        patch = dmp.patch_make terminal.lastScreen, scr
+        patch = dmp.patch_make terminal.lastScreen, scr
         terminal.lastScreen = scr
         callbacks.data patch, nr++
       ,10
-
+
       _lastMessageProcessed = 0
       _orderedMessages = {}

       consumeMessages = ->
-
+
         while _orderedMessages[_lastMessageProcessed]
           terminal.write _orderedMessages[_lastMessageProcessed].cmd
           delete _orderedMessages[_lastMessageProcessed]
           _lastMessageProcessed++
           # console.log _orderedMessages,_lastMessageProcessed
-
+
         ###
         for key,o of _orderedMessages
           do (key)->
@@ -59,9 +57,9 @@
               setTimeout ->
                 # console.log "skipping ahead.. missing keys didn't arrive in one sec.",{key,_lastMessageProcessed}

-                #
+                #
                 if _lastMessageProcessed < key
-                  _lastMessageProcessed = key
+                  _lastMessageProcessed = key
                   delete _orderedMessages[k] for k,oo in _orderedMessages when k < key

                 # consumeMessages()
@@ -77,17 +75,17 @@
           # _orderedMessages[d[0]] = group:d[1],time:d[2],cmd:d[3] for d in data
           # _orderedMessages = _.sortBy _orderedMessages,((e)-> return e[0])
           # console.log {data}
-
+
           terminal.write d[3] for d in data
           # consumeMessages()
-
+
           # process = (msg)->
           #   baseTime = msg[0][1]
           #   sendKeystroke = (bufferedKeystroke)->
           #     setTimeout (-> terminal.write bufferedKeystroke[0]),bufferedKeystroke[1]-baseTime
           #   sendKeystroke(cmd) for cmd in msg
-          #
-          #
+          #
+          #
           # _orderedMessages[messageNum] = data
           # # console.log _orderedMessages,_lastMessageProcessed
           # do (messageNum) ->
@@ -114,7 +112,7 @@
           #       else
           #         console.log "we waited for screenNr:#{messageNum} and it did arrive before 1sec."
           #     ,1000
-
+
         resize             : (rows, cols) -> terminal.setScreenSize rows, cols
         close              : ()->
           console.log "close is called"


PUBDEV-344 Change weights/biases into links to 2d tables

--- src/ext/modules/routines.coffee
+++ src/ext/modules/routines.coffee
@@ -593,7 +593,7 @@
           inspections["#{name} - #{v.name}"] = inspectTwoDimTable_ origin, "#{name} - #{v.name}", v
         else
           if isArray v
-            if k is 'cross_validation_models' or k is 'cross_validation_predictions' # megahack
+            if k is 'cross_validation_models' or k is 'cross_validation_predictions' or (name is 'output' and (k is 'weights' or k is 'biases')) # megahack
               inspections[k] = inspectObjectArray_ k, origin, k, v
             else
               inspections[k] = inspectRawArray_ k, origin, k, v


Fixed coffeelint.

Fixed coffeelint.

--- app/assets/javascripts/app/controllers/widget/online_notification.coffee
+++ app/assets/javascripts/app/controllers/widget/online_notification.coffee
@@ -94,7 +94,7 @@
     notificationsContainer.on 'click', @stopPropagation
     $(window).on 'click.notifications', @hidePopover

-  onHide: =>
+  onHide: ->
     $(window).off 'click.notifications'

   hidePopover: =>


Implemented fail-safe for autoload of non-function exports

--- app/init.coffee
+++ app/init.coffee
@@ -27,6 +27,9 @@
       if stat.isDirectory()
         autoload filepath, obj, logcat, args
       else if not obj[cls]?
+        req = require filepath
+        continue if typeof req isnt 'function'
+
         if args?
           if not Array.isArray args
             args = [args]
@@ -36,7 +39,7 @@
         if logcat? and typeof logging is 'function'
           args.push logger "#{logcat}.#{cls}"

-        obj[cls] = require(filepath).apply null, args
+        obj[cls] = req.apply null, args
         app.log?.info "Loaded {#{cls}} from #{filepath}"

   readonly_config = {


carets working properly now

carets working properly now

--- app/assets/javascripts/ckeditor/plugins/effective_menus/plugin.js.coffee
+++ app/assets/javascripts/ckeditor/plugins/effective_menus/plugin.js.coffee
@@ -6,7 +6,7 @@
   class EffectiveMenuEditor
     defaults:
       menuClass: 'effective-menu'
-      expandThreshold: 600  # Seconds before a leaf li item will be auto-expanded into a dropdown
+      expandThreshold: 100  # Seconds before a leaf li item will be auto-expanded into a dropdown

     menu: null
     draggable: null
@@ -69,7 +69,7 @@

         @markDestroyed(@draggable)

-        @cleanupAfterDrop()
+        @cleanupMenuAfterDrop()
         event.stopPropagation()
         event.preventDefault()

@@ -119,7 +119,7 @@
         return false unless @draggable
         item = $(event.currentTarget)

-        @cleanupAfterDrop()
+        @cleanupMenuAfterDrop()
         item.css('opacity', '1.0')

       @menu.on 'drop', 'li', (event) =>
@@ -128,11 +128,13 @@
         # Don't allow to drop into myself or my own children
         return false if !@draggable? || @draggable.is(item) || @draggable.find(item).length > 0

-        item.before(event.originalEvent.dataTransfer.getData('text/html'))
+        new_item = $(event.originalEvent.dataTransfer.getData('text/html'))
+
+        item.before(new_item)
         @draggable.remove()

-        @cleanupAfterDrop()
-        item.parentsUntil(@menu, 'li.dropdown').addClass('open')
+        @cleanupMenuAfterDrop()
+        @cleanupItemAfterDrop(new_item)

         event.stopPropagation()
         event.preventDefault()
@@ -154,12 +156,12 @@
       @menu.find('.open').removeClass('open')
       item.parentsUntil(@menu, 'li.dropdown').addClass('open')

-    cleanupAfterDrop: ->
+    cleanupMenuAfterDrop: ->
       # chrome puts in a weird meta tag that firefox doesnt
       # so we delete it here
       @menu.find('meta,li.effective-menu-expand').remove()

-      # Convert any empty dropdowns back we converted back to leafs
+      # Collapse any empty dropdowns we may have expanded back to leafs
       @menu.find('.dropdown-menu:empty').each (index, item) ->
         item = $(item).closest('.dropdown')
         item.removeClass('dropdown').removeClass('open')
@@ -167,11 +169,21 @@
         item.children('.dropdown-menu').remove()

       @menu.removeClass('dragging')
+      @menu.children('.actions').children('.add-item').show()
       @menu.find('.placeholder,.open').removeClass('placeholder').removeClass('open')
-      @menu.children('.actions').children('.add-item').show()

       @draggable = null
       @droppable = null
+
+    cleanupItemAfterDrop: (item) ->
+      item.children('a').find('span.caret').remove() # Just always remove the caret if present
+
+      # And add it back if we're a top level node
+      if item.parent().hasClass('effective-menu') && item.children('.dropdown-menu').length
+        item.children('a').append("<span class='caret'>")
+
+      item.parentsUntil(@menu, 'li.dropdown').addClass('open')
+

     # Just pass _destroyed = 1 back to rails to delete this item
     # Rails seems to disregard new items set to the new Date.now() values anyhow


error beeps and shows message

error beeps and shows message

--- src/layout/mixins/events.coffee
+++ src/layout/mixins/events.coffee
@@ -9,13 +9,15 @@
   (duration, type, on_end) ->
     duration = +duration
     type = (type % 5) || 0 # Only 0-4 are valid types.
-    on_end = () -> "beep" unless _.isFunction on_end
+    on_end = (-> "beep") unless _.isFunction on_end

     speaker = context.createOscillator()
     speaker.type = type
     speaker.connect context.destination
     speaker.noteOn 0
-    turn_off = -> speaker.noteOff(0); on_end();
+    turn_off = ->
+      speaker.noteOff 0
+      on_end()
     setTimeout turn_off, duration

 exports.events = {
@@ -50,8 +52,7 @@
     step_and_repeat(step_and_repeat)

   error: (world, message) ->
-    console.error message
-    beep(my.duration.tone, 1)
+    beep my.duration.tone, 3, -> alert("Error: #{message}")

   done: (world, args) ->
     success = args > 0


Tell the toolbar to update active status in removeLink

--- src/modules/link-tooltip.coffee
+++ src/modules/link-tooltip.coffee
@@ -44,6 +44,7 @@
     )
     this.initTextbox(@textbox, this.saveLink, this.hide)
     @quill.onModuleLoad('toolbar', (toolbar) =>
+      @toolbar = toolbar
       toolbar.initFormat('link', _.bind(this._onToolbar, this))
     )

@@ -62,6 +63,7 @@
     if range.isCollapsed()
       range = this._expandRange(range)
     @quill.formatText(range, 'link', false, 'user')
+    @toolbar.setActive('link', false) if @toolbar?

   setMode: (url, edit = false) ->
     if edit


default options to empty object; re-add relistener

--- lib/index.coffee
+++ lib/index.coffee
@@ -4,6 +4,9 @@

 # use this to read certificate files
 readCerts = require './read-certs'
+
+# handles EADDRINUSE error by retrying to listen()
+relistener = require './relistener'

 # provides the `cio.use()` function to add plugins
 getPlugin = require './get-plugin'
@@ -18,7 +21,7 @@
   chain = buildPluginChain builderOptions

   # build socket builder function
-  builder = (options, isServer) ->
+  builder = (options = {}, isServer) ->

     # if there are cert file paths specified, read the files now
     readCerts options
@@ -59,6 +62,15 @@
     if options.onConnect?
       socket.on (if isServer then 'connection' else 'connect'), options.onConnect

+    # I'm choosing to keep the `relistener` ability in `cio` and adding it
+    # unless specified *not* to. It's a simple helper for an annoying issue.
+    if isServer and options.relistener isnt false
+      relistener
+        server    : socket
+        retryDelay: options.retryDelay
+        maxRetries: options.maxRetries
+        relisten  : options.relisten
+
     # 5. all done
     return socket


include default connect event name in context

--- lib/index.coffee
+++ lib/index.coffee
@@ -56,6 +56,10 @@
         isServer: isServer
         isSecure: isSecure
         options : options
+        connectEvent:
+          if isSecure and isServer then 'secureConnection'
+          else if isServer then 'connection'
+          else 'connect'

     # if the chain failed then return an error back along with info
     if result.error? then return error:'Failed to configure socket', reason:result


assert with each instead of map

assert with each instead of map

--- lib/index.coffee
+++ lib/index.coffee
@@ -26,8 +26,8 @@
 understream.mixin fs.createWriteStream, 'writeFile', true
 understream.mixin (test, msg) ->
   _.stream()
-    .map (obj, cb) ->
-      return setImmediate cb, null, obj if test obj
+    .each (obj, cb) ->
+      return cb() if test obj
       cb new Error msg
     .stream()
 , 'assert', true


Change: Replaced errors and 404 with next calls (middleware style)

Signed-off-by: Christian Roth <396398d2f664a436582eed8a8cfb08dd7671c8d6@port17.de>

--- lib/app/Dispatcher.coffee
+++ lib/app/Dispatcher.coffee
@@ -51,7 +51,7 @@

     try
       clazz = new clazz()
-      clazz[method]( req, res )
+      clazz[method]( req, res, next )
     catch err
       return next new Error "Unable to execute method, because #{err}"


feat: Scope xpath to root of document

feat: Scope xpath to root of document

If we put priority on which tags are present for Signature validation
(AuthnRequest < Response < Assertion), it's possible for a crafted SAML request
to validate the signature for a particular tag, which is invalid for the
request.

Guard against this by selecting for the signature by starting from the root
document element, as we've done so in the past.

--- lib/saml2.coffee
+++ lib/saml2.coffee
@@ -248,15 +248,9 @@
   xml = _xml.replace(/\r\n?/g, '\n')
   doc = (new xmldom.DOMParser()).parseFromString(xml)

-  # Find the correct section of the XML doc to check the signature for
-  maybe_req = xmlcrypto.xpath(doc, "//*[local-name(.)='AuthnRequest']")
-  maybe_req = maybe_req && maybe_req[0]
-  maybe_resp = xmlcrypto.xpath(doc, "//*[local-name(.)='Response']")
-  maybe_resp = maybe_resp && maybe_resp[0]
-  maybe_assert = xmlcrypto.xpath(doc, "//*[local-name(.)='Assertion']")
-  maybe_assert = maybe_assert && maybe_assert[0]
-  to_check = maybe_req || maybe_resp || maybe_assert
-  signature = xmlcrypto.xpath(to_check, "./*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']")
+  # xpath failed to capture <ds:Signature> nodes of direct descendents of the root.
+  # Call documentElement to explicitly start from the root element of the document.
+  signature = xmlcrypto.xpath(doc.documentElement, "./*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']")
   return null unless signature.length is 1
   sig = new xmlcrypto.SignedXml()
   sig.keyInfoProvider = getKey: -> format_pem(certificate, 'CERTIFICATE')


capture leave event

capture leave event

--- app/index.coffee
+++ app/index.coffee
@@ -15,6 +15,8 @@
 seasons = require 'lib/seasons'
 TopBar = require 'zooniverse/lib/controllers/top_bar'
 User = require 'zooniverse/lib/models/user'
+Subject = require 'models/subject'
+AnalyticsLogger = require 'lib/analytics'
 googleAnalytics = require 'zooniverse/lib/google_analytics'
 # Map = require 'zooniverse/lib/map'

@@ -105,6 +107,11 @@
       app.topBar.loginForm.signInButton.click()
       app.topBar.loginDialog.reattach()

+    $(window).bind('beforeunload', (e) ->
+        AnalyticsLogger.logEvent User.current?.zooniverse_id,Subject.current?.zooniverseId,'leave',''
+        event.preventDefault()
+    )
+
     app.stack.el.appendTo 'body'
     app.topBar.el.prependTo 'body'


Updated Bulgarian translation

Updated Bulgarian translation

--- app/locale/bg.coffee
+++ app/locale/bg.coffee
@@ -526,42 +526,42 @@
     restricted_to_certain_heroes: "Само определени герои могат да играят това ниво."

 #  skill_docs:
-#    writable: "writable" # Hover over "attack" in Your Skills while playing a level to see most of this
-#    read_only: "read-only"
-#    action: "Action"
-#    spell: "Spell"
-#    action_name: "name"
-#    action_cooldown: "Takes"
-#    action_specific_cooldown: "Cooldown"
-#    action_damage: "Damage"
-#    action_range: "Range"
-#    action_radius: "Radius"
-#    action_duration: "Duration"
-#    example: "Example"
-#    ex: "ex" # Abbreviation of "example"
-#    current_value: "Current Value"
-#    default_value: "Default value"
-#    parameters: "Parameters"
-#    returns: "Returns"
+     writable: "заменяем" # Hover over "attack" in Your Skills while playing a level to see most of this
+     read_only: "незаменяем"
+     action: "Действие"
+     spell: "Магия"
+     action_name: "име"
+     action_cooldown: "Takes"
+     action_specific_cooldown: "Изчакване"
+     action_damage: "Удар"
+     action_range: "Обхват"
+     action_radius: "Радиус"
+     action_duration: "Времетраене"
+     example: "Пример"
+     ex: "пр." # Abbreviation of "example"
+     current_value: "Текуща стойност"
+     default_value: "Стойност по подразбиране"
+     parameters: "Параметри"
+     returns: "Връща"
 #    granted_by: "Granted by"

 #  save_load:
-#    granularity_saved_games: "Saved"
-#    granularity_change_history: "History"
+     granularity_saved_games: "Записани"
+     granularity_change_history: "История"

 #  options:
-#    general_options: "General Options" # Check out the Options tab in the Game Menu while playing a level
-#    volume_label: "Volume"
-#    music_label: "Music"
-#    music_description: "Turn background music on/off."
-#    editor_config: "Editor Config"
-#    editor_config_title: "Editor Configuration"
-#    editor_config_level_language_label: "Language for This Level"
-#    editor_config_level_language_description: "Define the programming language for this particular level."
-#    editor_config_default_language_label: "Default Programming Language"
+     general_options: "Общи настройки" # Check out the Options tab in the Game Menu while playing a level
+     volume_label: "Звук"
+     music_label: "Музика"
+     music_description: "Включи/изключи фоновата музика"
+     editor_config: "Настройки на редактора"
+     editor_config_title: "Конфигурация на редактора"
+     editor_config_level_language_label: "Език за това ниво"
+     editor_config_level_language_description: "Определи езика за програмиране за това ниво."
+     editor_config_default_language_label: "Програмен език по подразбиране"
 #    editor_config_default_language_description: "Define the programming language you want to code in when starting new levels."
-#    editor_config_keybindings_label: "Key Bindings"
-#    editor_config_keybindings_default: "Default (Ace)"
+     editor_config_keybindings_label: "Клавишни комбинации"
+     editor_config_keybindings_default: "По подразбиране (Ace)"
 #    editor_config_keybindings_description: "Adds additional shortcuts known from the common editors."
 #    editor_config_livecompletion_label: "Live Autocompletion"
 #    editor_config_livecompletion_description: "Displays autocomplete suggestions while typing."


Update da.coffee

Update da.coffee

--- app/locale/da.coffee
+++ app/locale/da.coffee
@@ -31,7 +31,7 @@
     contact: "Kontakt"
     twitter_follow: "Følg"
     teachers: "Lærere"
-#    careers: "Careers"
+    careers: "Karrierer"

   modal:
     close: "Luk"
@@ -80,7 +80,7 @@
     adjust_volume: "Indstil lydstyrke"
     campaign_multiplayer: "Multiplayer Arenaer"
     campaign_multiplayer_description: "... hvor du koder ansigt-til-ansigt imod andre spillere."
-#    campaign_old_multiplayer: "(Deprecated) Old Multiplayer Arenas"
+    campaign_old_multiplayer: "(Forældet) Gammel version af Multiplayer Arenaer"
 #    campaign_old_multiplayer_description: "Relics of a more civilized age. No simulations are run for these older, hero-less multiplayer arenas."

   share_progress_modal:
@@ -159,7 +159,7 @@
     accepted: "Accepteret"
     rejected: "Afvist"
     withdrawn: "Trukket tilbage"
-#    accept: "Accept"
+    accept: "Accepter"
 #    reject: "Reject"
 #    withdraw: "Withdraw"
     submitter: "Indsender"
@@ -196,7 +196,7 @@
     player: "Spiller"
     player_level: "Niveau" # Like player level 5, not like level: Dungeons of Kithgard
     warrior: "Krigsherre"
-#    ranger: "Ranger"
+    ranger: "Bueskytte"
     wizard: "Troldmand"

   units:
@@ -217,8 +217,8 @@

   play_level:
     done: "Færdig"
-#    next_game: "Next game"
-#    show_menu: "Show game menu"
+    next_game: "Næste spil"
+    show_menu: "Vis spil menu"
     home: "Hjem" # Not used any more, will be removed soon.
     level: "Bane" # Like "Level: Dungeons of Kithgard"
     skip: "Spring over"
@@ -251,7 +251,7 @@
     victory_saving_progress: "Gemmer fremskridt"
     victory_go_home: "Gå hjem"
     victory_review: "Fortæl os mere!"
-#    victory_review_placeholder: "How was the level?"
+    victory_review_placeholder: "Hvordan var levelet?"
     victory_hour_of_code_done: "Er du færdig?"
     victory_hour_of_code_done_yes: "Ja, jeg er færdig med min Kodetime!"
     victory_experience_gained: "XP tjent"
@@ -260,7 +260,7 @@
     victory_viking_code_school: "For dælen det var en svær bane du lige slog! Hvis ikke du allerede er softwareudvikler, så burde du blive det. Du er lige kommet foran i køen til at blive accepteret hos Viking Code School, du kan tage dine evner til det næste niveau og blive en professionel webudvikler på 14 uger."
     victory_become_a_viking: "Bliv en Viking"
 #    victory_bloc: "Great work! Your skills are improving, and someone's taking notice. If you've considered becoming a software developer, this may be your lucky day. Bloc is an online bootcamp that pairs you 1-on-1 with an expert mentor who will help train you into a professional developer! By beating A Mayhem of Munchkins, you're now eligible for a $500 price reduction with the code: CCRULES"
-#    victory_bloc_cta: "Meet your mentor – learn about Bloc"
+    victory_bloc_cta: "Mød din mentor - Hør mere om Bloc"
     guide_title: "Instruktioner"
     tome_minion_spells: "Dine Minions' besværgelser" # Only in old-style levels.
     tome_read_only_spells: "Læsebesværgelser" # Only in old-style levels.
@@ -298,7 +298,7 @@
     tip_scrub_shortcut: "Brug Ctrl+[ og Ctrl+] til at spole tilbage og frem."
     tip_guide_exists: "Klik på guiden i spilmenuen (i toppen af siden) for brugbar info."
     tip_open_source: "CodeCombat er 100% open source!"
-#    tip_tell_friends: "Enjoying CodeCombat? Tell your friends about us!"
+    tip_tell_friends: "Kan du lide CodeCombat? Fortæl dine venner om os!"
     tip_beta_launch: "CodeCombat søsatte sin beta i oktober, 2013."
     tip_think_solution: "Tænk på løsningen, ikke problemet."
     tip_theory_practice: "I teorien er der ingen forskel på teori og praksis. Men i praksis er der. - Yogi Bjørn"
@@ -327,23 +327,23 @@
 #    tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
 #    tip_superpower: "Coding is the closest thing we have to a superpower."
 #    tip_control_destiny: "In real open source, you have the right to control your own destiny. - Linus Torvalds"
-#    tip_no_code: "No code is faster than no code."
-#    tip_code_never_lies: "Code never lies, comments sometimes do. — Ron Jeffries"
-#    tip_reusable_software: "Before software can be reusable it first has to be usable."
+    tip_no_code: "Ingen kode er hurtigerer end ingen kode."
+    tip_code_never_lies: "Kode lyver aldrig, kommentarer gør nogle gange. - Ron Jeffries"
+    tip_reusable_software: "Før software kan være genbrugeligt skal det først være brugbart."
 #    tip_optimization_operator: "Every language has an optimization operator. In most languages that operator is ‘//’"
 #    tip_lines_of_code: "Measuring programming progress by lines of code is like measuring aircraft building progress by weight. — Bill Gates"
 #    tip_source_code: "I want to change the world but they would not give me the source code."
 #    tip_javascript_java: "Java is to JavaScript what Car is to Carpet. - Chris Heilmann"
-#    tip_move_forward: "Whatever you do, keep moving forward. - Martin Luther King Jr."
+    tip_move_forward: "Hvad end du gør, så bliv ved med at rykke fremad. - Martin Luther King Jr."
     tip_google: "Har du et problem du ikke kan løse? Google det!"
     tip_adding_evil: "Tilføjer et strejf af ondskab.."
 #    tip_hate_computers: "That's the thing about people who think they hate computers. What they really hate is lousy programmers. - Larry Niven"
 #    tip_open_source_contribute: "You can help CodeCombat improve!"
 #    tip_recurse: "To iterate is human, to recurse divine. - L. Peter Deutsch"
 #    tip_free_your_mind: "You have to let it all go, Neo. Fear, doubt, and disbelief. Free your mind. - Morpheus"
-#    tip_strong_opponents: "Even the strongest of opponents always has a weakness. - Itachi Uchiha"
+    tip_strong_opponents: "Selv de stærkeste modstandere har en svaghed. - Itachi Uchiha"
     tip_paper_and_pen: "Før du starter med at programmere, kan du altid sætte dig ned med et stykke papir og blyant."
-#    tip_solve_then_write: "First, solve the problem. Then, write the code. - John Johnson"
+    tip_solve_then_write: "Først, løs problemet, derefter skriv koden. - John Johnson"

   game_menu:
     inventory_tab: "Dine ting"
@@ -367,14 +367,14 @@
 #    view_other_solutions: "View Leaderboards"
 #    scores: "Scores"
 #    top_players: "Top Players by"
-#    day: "Today"
-#    week: "This Week"
+    day: "Idag"
+    week: "Denne uge"
 #    all: "All-Time"
-#    time: "Time"
+    time: "Tid"
 #    damage_taken: "Damage Taken"
 #    damage_dealt: "Damage Dealt"
     difficulty: "Sværhedsgrad"
-#    gold_collected: "Gold Collected"
+    gold_collected: "Guld samlet"

 #  inventory:
 #    choose_inventory: "Equip Items"
@@ -489,43 +489,43 @@
 #    subscribe_prepaid: "Click Subscribe to use prepaid code"
 #    using_prepaid: "Using prepaid code for monthly subscription"

-#  choose_hero:
-#    choose_hero: "Choose Your Hero"
-#    programming_language: "Programming Language"
-#    programming_language_description: "Which programming language do you want to use?"
-#    default: "Default"
-#    experimental: "Experimental"
-#    python_blurb: "Simple yet powerful, great for beginners and experts."
-#    javascript_blurb: "The language of the web. (Not the same as Java.)"
-#    coffeescript_blurb: "Nicer JavaScript syntax."
-#    clojure_blurb: "A modern Lisp."
-#    lua_blurb: "Game scripting language."
-#    io_blurb: "Simple but obscure."
-#    status: "Status"
-#    hero_type: "Type"
-#    weapons: "Weapons"
-#    weapons_warrior: "Swords - Short Range, No Magic"
-#    weapons_ranger: "Crossbows, Guns - Long Range, No Magic"
-#    weapons_wizard: "Wands, Staffs - Long Range, Magic"
+  choose_hero:
+    choose_hero: "Vælg din helt"
+    programming_language: "Programmerings sprog"
+    programming_language_description: "Hvilket programmerings sprog har du lyst til at bruge?"
+    default: "Standard"
+    experimental: "Experimental"
+    python_blurb: "Simplet, dog stærkt, godt for begyndere og eksperter."
+    javascript_blurb: "Internettets sprog. (Ikke det samme som Java.)"
+    coffeescript_blurb: "Pænere JavaScript syntax."
+    clojure_blurb: "En moderne version af Lisp."
+    lua_blurb: " Spil scripting sprog."
+#    io_blurb: "Simple but obscure."
+    status: "Status"
+    hero_type: "Type"
+    weapons: "Våben"
+    weapons_warrior: "Sværd - Kort afstand, Ingen Magi"
+    weapons_ranger: "Armbryst, Skydevåben - Lang afstand, Ingen Magi"
+    weapons_wizard: "Tryllestave, Stave - Lang afstand, Magi"
 #    attack: "Damage" # Can also translate as "Attack"
-#    health: "Health"
-#    speed: "Speed"
-#    regeneration: "Regeneration"
+    health: "Liv"
+    speed: "Fart"
+    regeneration: "Regeneration"
 #    range: "Range" # As in "attack or visual range"
 #    blocks: "Blocks" # As in "this shield blocks this much damage"
 #    backstab: "Backstab" # As in "this dagger does this much backstab damage"
-#    skills: "Skills"
+    skills: "Færdigheder"
 #    attack_1: "Deals"
 #    attack_2: "of listed"
 #    attack_3: "weapon damage."
 #    health_1: "Gains"
 #    health_2: "of listed"
 #    health_3: "armor health."
-#    speed_1: "Moves at"
-#    speed_2: "meters per second."
-#    available_for_purchase: "Available for Purchase" # Shows up when you have unlocked, but not purchased, a hero in the hero store
-#    level_to_unlock: "Level to unlock:" # Label for which level you have to beat to unlock a particular hero (click a locked hero in the store to see)
-#    restricted_to_certain_heroes: "Only certain heroes can play this level."
+    speed_1: "Rykker med"
+    speed_2: "meter i sekundet."
+    available_for_purchase: "Kan nu blive købt" # Shows up when you have unlocked, but not purchased, a hero in the hero store
+    level_to_unlock: "Level for at låse op:" # Label for which level you have to beat to unlock a particular hero (click a locked hero in the store to see)
+    restricted_to_certain_heroes: "Kun visse helt kan spille dette level."

 #  skill_docs:
 #    writable: "writable" # Hover over "attack" in Your Skills while playing a level to see most of this


Won't need a context menu.

Won't need a context menu.

--- menus/jumpy.cson
+++ menus/jumpy.cson
@@ -1,8 +1,4 @@
 # See https://atom.io/docs/latest/creating-a-package#menus for more details
-'context-menu':
-  '.overlayer':
-      'Enable jumpy': 'jumpy:toggle'
-
 'menu': [
   {
     'label': 'Packages'


made calls to FakeSocket.on() chainable

made calls to FakeSocket.on() chainable

like the real thing

--- client/init.coffee
+++ client/init.coffee
@@ -3,7 +3,7 @@
     class FakeSocket
       emit: (e, a...) -> @other.onevent e: e, data: a
       onevent: ({e, data}) -> (for f in @[e] or [] then f data...); return
-      on: (e, f) -> (@[e] ?= []).push f; return
+      on: (e, f) -> (@[e] ?= []).push f; @
     socket = new FakeSocket; socket1 = new FakeSocket; socket.other = socket1; socket1.other = socket
     require('./proxy').Proxy()(socket1)
   else


Changed ListForm to allow for events on the form

--- client/list.coffee
+++ client/list.coffee
@@ -411,6 +411,32 @@

 UI.registerHelper 'ListForm', () ->
   console.log 'ListForm Start', this
+
+  listFormEvents =
+    'click .close-form': (event, template) ->
+      event.preventDefault()
+      data = template.data.data
+      element = $("##{data._id}")
+      if element.hasClass('hidden')
+          $('.form-row').addClass('hidden')
+          element.removeClass('hidden')
+        else
+          element.addClass('hidden')
+        return false
+
+    'click .show-created-updated': (event, template) ->
+      event.preventDefault()
+      console.log 'Clicked show-created-updated'
+      show = Session.get('showDateTime')
+      showValue =
+        switch show
+          when true then false
+          when false then true
+          else true
+
+      Session.set('showDateTime', showValue)
+      return false
+
   data = this.data
   if data.context?.list?.form?
     form = data.context?.list?.form
@@ -423,6 +449,14 @@
     # console.log 'Last else for ListForm.', form

   fields = []
+  if form.events?
+    console.log 'Working on _listForm.events'
+
+    events = Object.merge(form.events, listFormEvents)
+    console.log events
+    Template._listForm.events = events
+  else
+    Template._listForm.events = listFormEvents

   if form?.css?
     this.css = form.css
@@ -470,30 +504,10 @@
   console.log 'ListForm End', this
   return Template._listForm

-Template._listForm.events
-  'click .close-form': (event, template) ->
-    event.preventDefault()
-    data = template.data.data
-    element = $("##{data._id}")
-    if element.hasClass('hidden')
-        $('.form-row').addClass('hidden')
-        element.removeClass('hidden')
-      else
-        element.addClass('hidden')
-      return false
-
-  'click .show-created-updated': (event, template) ->
-    event.preventDefault()
-    console.log 'Clicked show-created-updated'
-    show = Session.get('showDateTime')
-    showValue =
-      switch show
-        when true then false
-        when false then true
-        else true
-
-    Session.set('showDateTime', showValue)
-    return false
+
+
+
+

 Template._listForm.helpers
   showDateTime: () ->


use StringWithComponent in renderDeletedBy

--- resources/assets/coffee/react/_components/comment.coffee
+++ resources/assets/coffee/react/_components/comment.coffee
@@ -12,7 +12,10 @@
 import { ReportReportable } from 'report-reportable'
 import ShowMoreLink from 'show-more-link'
 import { Spinner } from 'spinner'
+import { StringWithComponent } from 'string-with-component'
+import TimeWithTooltip from 'time-with-tooltip'
 import UserAvatar from 'user-avatar'
+import { UserLink } from 'user-link'
 import { classWithModifiers } from 'utils/css'
 import { estimateMinLines } from 'utils/estimate-min-lines'
 import { createClickCallback, formatNumberSuffixed } from 'utils/html'
@@ -222,14 +225,18 @@
   renderDeletedBy: =>
     if @props.comment.isDeleted && @props.comment.canModerate
       deleter = userStore.get(@props.comment.deletedById)
-      div
-        className: 'comment__row-item comment__row-item--info'
-        dangerouslySetInnerHTML:
-          __html: osu.trans 'comments.deleted_by',
-            timeago: osu.timeago(@props.comment.deletedAt)
-            user:
+      div className: 'comment__row-item comment__row-item--info',
+        el StringWithComponent,
+          pattern: osu.trans('comments.deleted_by')
+          mappings:
+            ':timeago':
+              el TimeWithTooltip,
+                key: 'timeago'
+                dateTime: @props.comment.deletedAt
+                relative: true
+            ':user':
               if deleter.id?
-                osu.link(laroute.route('users.show', user: deleter.id), deleter.username)
+                el(UserLink, key: 'user', user: deleter)
               else
                 _.escape deleter.username


Formatting

Formatting

--- scripts/node-auth-token.coffee
+++ scripts/node-auth-token.coffee
@@ -1,10 +1,9 @@
 # Description:
-#   Link for adding NODE_AUTH_TOKEN for private node repos.
+#   Link for adding node_auth_token for private node repos.
 #
 # Commands:
-#   hubot NODE_AUTH_TOKEN - Reply with link
-
+#   hubot node_auth_token me- Reply with link

 module.exports = (robot) ->
-  robot.respond /NODE_AUTH_TOKEN me/i, (msg) ->
-    msg.send "https://ghdocs.grubhub.com/architecture/platform/standards-and-practices/github/actions/packages/"+  robot.respond /node_auth_token me/i, (msg) ->
+    msg.send "https://ghdocs.grubhub.com/architecture/platform/standards-and-practices/github/actions/packages/"


Update languageCodeFromRequest to exclude new locale functions

--- server/routes/languages.coffee
+++ server/routes/languages.coffee
@@ -46,7 +46,7 @@

 module.exports.languageCodeFromRequest = languageCodeFromRequest = (req) ->
-  possibleCodes = _.without _.keys(locale).concat(_.keys(languageAliases)), 'update'
+  possibleCodes = _.without _.keys(locale).concat(_.keys(languageAliases)), 'update', 'load', 'storeLoadedLanguage', 'installVueI18n'
   code = req.acceptsLanguages(possibleCodes) or 'en-US'
   code = languageAliases[code.toLowerCase()] or code
   code


fix cpu halt

fix cpu halt

--- server/startup/presence.coffee
+++ server/startup/presence.coffee
@@ -2,5 +2,5 @@
 	InstanceStatus.registerInstance('rocket.chat', {port: process.env.PORT})
 	# InstanceStatus.activeLogs()
 	UserPresence.start()
-	# UserPresence.activeLogs()
-	UserPresenceMonitor.start()
+	# # UserPresence.activeLogs()
+	# UserPresenceMonitor.start()


pass access_token around to not race on it

--- server/utils/sync_gmail.coffee
+++ server/utils/sync_gmail.coffee
@@ -19,6 +19,7 @@
             login: profile.emails[0].value
             oauthProvider: "GMAIL"
             initialized: false
+            oauthAccessToken: access_token
             oauthRefreshToken: refresh_token   # RefreshToken (in order to get an access_token)

         Account.request 'byEmailWithOauth', key: profile.emails[0].value , (err, fetchedAccounts)->


#208  generate summary table for all commands

--- lib/developer.coffee
+++ lib/developer.coffee
@@ -43,7 +43,10 @@

       'open-in-vim': => @openInVim()
       'generate-introspection-report': => @generateIntrospectionReport()
-      'report-commands-have-no-default-keymap': => @reportCommandsHaveNoDefaultKeymap()
+      'generate-command-summary-table-for-commands-have-no-default-keymap': =>
+        @generateCommandSummaryTableForCommandsHaveNoDefaultKeymap()
+      'generate-command-summary-table': =>
+        @generateCommandSummaryTable()
       'toggle-dev-environment': => @toggleDevEnvironment()
       'reload-packages': => @reloadPackages()
       'toggle-reload-packages-on-save': => @toggleReloadPackagesOnSave()
@@ -109,29 +112,105 @@
     settings.set('debug', not settings.get('debug'))
     console.log "#{settings.scope} debug:", settings.get('debug')

-  reportCommandsHaveNoDefaultKeymap: ->
-    packPath = atom.packages.resolvePackagePath('vim-mode-plus')
+  getCommandSpecs: ->
+    compactSelector = (selector) ->
+      selector.split(/,\s*/g).map (scope) ->
+        scope
+          .replace(/atom-text-editor\.vim-mode-plus/, '')
+          .replace(/:not\((.*)\)/, '!$1')
+          .replace(/\.normal-mode/, 'n')
+          .replace(/\.visual-mode\.blockwise/, 'vB')
+          .replace(/\.visual-mode\.linewise/, 'vL')
+          .replace(/\.visual-mode\.characterwise/, 'vC')
+          .replace(/\.visual-mode/, 'v')
+          .replace(/\.insert-mode\.replace/, 'iR')
+          .replace(/\.insert-mode/, 'i')
+          .replace(/\.operator-pending-mode/, 'o')
+      .join(",")
+
+    modifierKeyMap =
+      cmd: '\u2318'
+      ctrl: '\u2303'
+      alt: '\u2325'
+      option: '\u2325'
+      shift: '\u21e7'
+      enter: '\u23ce'
+      left: '\u2190'
+      right: '\u2192'
+      up: '\u2191'
+      down: '\u2193'
+
+    compactKeystrokes = (keystrokes) ->
+      keystrokes
+        .replace(/(`|_)/g, '\\$1')
+        .replace(/ctrl-/, modifierKeyMap["ctrl"])
+        .replace(/down|up|left|right|enter|cmd|option/, (s) -> modifierKeyMap[s])
+        .replace('backspace', 'BS')
+        .replace('space', 'SPC')
+        .replace(/\s+/, '')
+
     commands = (
       for name, klass of Base.getRegistries() when klass.isCommand()
         kind = getAncestors(klass).map((k) -> k.name)[-2..-2][0]
         commandName = klass.getCommandName()
         description = klass.getDesctiption()
-        {name, commandName, kind, description}
+
+        keymap = null
+        if keymaps = getKeyBindingForCommand(commandName)
+          keymap = keymaps.map ({keystrokes, selector}) ->
+            "`#{compactSelector(selector)}` #{compactKeystrokes(keystrokes)}"
+          .join("
")
+
+        # keystrokes#{keystrokes}"
+
+        {name, commandName, kind, description, keymap}
     )
-    commandsHaveNoKeymap = commands.filter (command) ->
-      not getKeyBindingForCommand(command.commandName)
-
-    report = [
-      "| Kind | Command | Description |"
-      "|:-----|:--------|:------------|"
-    ]
-    for {commandName, kind, description} in commandsHaveNoKeymap
-      commandName = commandName.replace(/vim-mode-plus:/, '')
-      description ?= ""
-      report.push "| #{kind} | #{commandName} | #{description} |"
+    commands
+
+  kinds = ["Operator", "Motion", "TextObject", "InsertMode", "Misc", "Scroll", "VisualBlockwise"]
+  generateSummaryTableForCommandSpecs: (specs, {header}={}) ->
+    grouped = _.groupBy(specs, 'kind')
+    str = ""
+    for kind in kinds
+      specs = grouped[kind]
+
+      report = [
+        "## #{kind}"
+        ""
+        "| Keymap | Command | Description |"
+        "|:-------|:--------|:------------|"
+      ]
+      for {keymap, commandName, description} in specs
+        commandName = commandName.replace(/vim-mode-plus:/, '')
+        description ?= ""
+        keymap ?= ""
+        report.push "| #{keymap} | `#{commandName}` | #{description} |"
+      str += report.join("\n") + "\n\n"

     atom.workspace.open().then (editor) ->
-      editor.setText report.join("\n")
+      editor.insertText(header + "\n") if header?
+      editor.insertText(str)
+
+  generateCommandSummaryTable: ->
+    header = """
+    # Description
+
+    - `!i`: :not(.insert-mode)
+    - `i`: insert-mode
+    - `o`: operator-pending-mode
+    - `n`: normal-mode
+    - `v`: visual-mode
+    - `vB`: visual-mode.blockwise
+    - `vL`: visual-mode.linewise
+    - `vC`: visual-mode.characterwise
+    - `iR`: insert-mode.replace
+
+    """
+    @generateSummaryTableForCommandSpecs(@getCommandSpecs(), {header})
+
+  generateCommandSummaryTableForCommandsHaveNoDefaultKeymap: ->
+    commands = @getCommandSpecs().filter (command) -> not getKeyBindingForCommand(command.commandName)
+    @generateSummaryTableForCommandSpecs(commands)

   openInVim: ->
     editor = atom.workspace.getActiveTextEditor()


dms setting isn't used

dms setting isn't used


Fix the build. support old style source map strings.

--- lib/extractor.coffee
+++ lib/extractor.coffee
@@ -1,7 +1,7 @@
 # Gratefully stolen from https://gist.github.com/pmuellr/5143384
 path = require "path"

-sourceMapCommentRegEx =  /\/\/[@#] sourceMappingURL=data:application\/json;(?:charset:[^;]+)?;base64,(.*)\n/
+sourceMapCommentRegEx =  /\/\/[@#] sourceMappingURL=data:application\/json(?:;charset:[^;]+)?;base64,(.*)\n/

 translateSources = (sources, grunt) ->
   newSources = []


feat(categoryOrderHint): add possibility to update by using a category slug

--- src/coffee/mapping.coffee
+++ src/coffee/mapping.coffee
@@ -135,6 +135,11 @@
         else if _.has(@categories.name2id, rawCatId)
           # get the actual category id instead of the category name
           @categories.name2id[rawCatId]
+        # in case the category was provided using the category slug
+        else if _.contains(@categories.id2slug, rawCatId)
+          # get the actual category id instead of the category name
+          _.findKey @categories.id2slug, (slug) ->
+            slug == rawCatId
         else
           msg = "[row #{rowIndex}:#{CONS.HEADER_CATEGORY_ORDER_HINTS}] Can not find category for ID '#{rawCatId}'!"
           if @continueOnProblems


Fix wrong FA path

Fix wrong FA path

--- battle-detail.coffee
+++ battle-detail.coffee
@@ -28,7 +28,7 @@

 document.title = __ 'Battle Detail'
-$('#font-awesome')?.setAttribute 'href', "#{ROOT}/components/font-awesome/css/font-awesome.min.css"
+$('#font-awesome')?.setAttribute 'href', require.resolve('font-awesome/css/font-awesome.css')

 require './views'


:shower: Remove unused helper function

:shower: Remove unused helper function

--- spec/display-layer-spec.coffee
+++ spec/display-layer-spec.coffee
@@ -987,11 +987,3 @@
 updateTokenLines = (tokenLines, displayLayer, changes) ->
   for {start, oldExtent, newExtent} in changes
     tokenLines.splice(start.row, oldExtent.row, getTokenLines(displayLayer, start.row, start.row + newExtent.row)...)
-
-logTokens = (displayLayer) ->
-  s = 'expectTokens(displayLayer, [\n'
-  for tokens in getTokenLines(displayLayer)
-    for {text, closeTags, openTags} in tokens
-      s += "  {text: '#{text}', close: #{JSON.stringify(closeTags)}, open: #{JSON.stringify(openTags)}},\n"
-  s += '])'
-  console.log s


Disable importer for now.

Disable importer for now.

--- src/compilers/sass.coffee
+++ src/compilers/sass.coffee
@@ -2,7 +2,7 @@

 {requireTry} = require '../utils'

-# look for .scss|sass files inside the node_modules folder
+# Look for .scss|sass files inside the node_modules folder
 resolveNpm = do ->
   cache = {}

@@ -20,6 +20,21 @@

     cb file: cache[url]

+# Add bourbon to include paths
+addBourbon = do ->
+  bourbonPath = null
+
+  (includePaths) ->
+    unless bourbonPath?
+      try
+        bourbonPath = path.dirname require.resolve 'bourbon'
+      catch err
+
+    if bourbonPath
+      includePaths.concat bourbonPath
+    else
+      includePaths
+
 module.exports = (opts, cb) ->
   sass = requireTry 'node-sass'

@@ -28,14 +43,12 @@
     path.dirname opts.absolutePath
   ]

-  try
-    bourbon = require 'node-bourbon'
-    includePaths = includePaths.concat bourbon.includePaths
-  catch err
+  # Try to include path to Bourbon
+  includePaths = addBourbon includePaths

   sass.render
+    # importer:     resolveNpm
     data:         opts.source
-    importer:     resolveNpm
     includePaths: includePaths
     outputStyle: 'nested'
   , (err, res) ->


better time indicator and keys alignement, fix #21

--- src/scripts/graph/Header.coffee
+++ src/scripts/graph/Header.coffee
@@ -68,7 +68,7 @@

     render: () =>
       timeSelection = @svgContainer.selectAll('.time-indicator')
-      timeSelection.attr('transform', 'translate(' + (@xDisplayed(@currentTime[0]) + 0.5) + ', 25)')
+      timeSelection.attr('transform', 'translate(' + (@xDisplayed(@currentTime[0])) + ', 25)')

     createTimeHandle: () =>
       self = this
@@ -113,11 +113,11 @@

       timeGrp = timeSelection.enter().append("g")
         .attr('class', "time-indicator")
-        .attr("transform", "translate(0," + 30 + ")")
+        .attr("transform", "translate(-0.5," + 30 + ")")
         .call(dragTime)
       timeGrp.append('rect')
         .attr('class', 'time-indicator__line')
-        .attr('x', -1)
+        .attr('x', -0.5)
         .attr('y', 0)
         .attr('width', 1)
         .attr('height', 1000)


optimized code to only require a single robot.respond

--- src/scripts/reddit-jokes.coffee
+++ src/scripts/reddit-jokes.coffee
@@ -22,22 +22,7 @@

 module.exports = (robot) ->

-  robot.respond /joke me/i, (msg) ->
-    msg.http('http://www.reddit.com/r/jokes.json')
-    .get() (err, res, body) ->
-      try
-        data = JSON.parse body
-        children = data.data.children
-        joke = msg.random(children).data
-
-        joketext = joke.title.replace(/\*\.\.\.$/,'') + ' ' + joke.selftext.replace(/^\.\.\.\s*/, '')
-
-        msg.send joketext.trim()
-
-      catch ex
-        msg.send "Erm, something went EXTREMELY wrong - #{ex}"
-
-  robot.respond /joke me ? (.+)/i, (msg) ->
+  robot.respond /joke me(.*)$/i, (msg) ->
     name = msg.match[1].trim()

     if name is "dad"


fix list component

fix list component

--- lib/carte/client/views/list.cjsx
+++ lib/carte/client/views/list.cjsx
@@ -21,18 +21,24 @@
       @props.cards.fetch()

   onClickAtoz: ()->
-      @setState query: {sort_key: 'title', sort_order: 'asc'}
-      @props.cards.query = @state.query
+      console.log 'atoz'
+      query = {sort_key: 'title', sort_order: 'asc'}
+      @setState query: query
+      @props.cards.query = query
       @props.cards.fetch()

   onClickLatest: ()->
-      @setState query: {sort_key: 'updated_at', sort_order: 'desc'}
-      @props.cards.query = @state.query
+      console.log 'late'
+      query = {sort_key: 'updated_at', sort_order: 'desc'}
+      @setState query: query
+      @props.cards.query = query
       @props.cards.fetch()

   onClickRandom: ()->
-      @setState query: {sort_order: 'random'}
-      @props.cards.query = @state.query
+      console.log 'rand'
+      query = {sort_order: 'random'}
+      @setState query: query
+      @props.cards.query = query
       @props.cards.fetch()

   render: ->


fake_data: use hyphaens to test for quote issues

--- bin/fake_data.coffee
+++ bin/fake_data.coffee
@@ -31,7 +31,7 @@
 toNS = (milliseconds) ->
   new Date(parseInt(milliseconds)) * SECONDS_TO_NS

-games = ['fruit_ninja', 'flappy_bird']
+games = ['fruit-ninja', 'flappy-bird']

 Promise.all [
   dropRethink()
@@ -48,31 +48,31 @@

   experiments = [
     {
-      apps: ['fruit_ninja', 'flappy_bird']
+      apps: ['fruit-ninja', 'flappy-bird']
       key: 'login_button'
       globalPercent: 100
       choices: ['control', 'blue']
     }
     {
-      apps: ['flappy_bird']
+      apps: ['flappy-bird']
       key: 'invite_landing'
       globalPercent: 100
       choices: ['control', 'purple', 'yellow']
     }
     {
-      apps: ['flappy_bird']
+      apps: ['flappy-bird']
       key: 'feedback'
       globalPercent: 10
       choices: ['control', 'visible']
     }
     {
-      apps: ['fruit_ninja']
+      apps: ['fruit-ninja']
       key: 'share_icon'
       globalPercent: 100
       choices: ['control', 'big_red', 'big_blue', 'small_red', 'small_blue']
     }
     {
-      apps: ['fruit_ninja']
+      apps: ['fruit-ninja']
       key: 'animation'
       globalPercent: 100
       choices: ['control', 'animated']


Update atom settings

Update atom settings

--- atom/config.cson
+++ atom/config.cson
@@ -17,23 +17,26 @@
     userId: "9e4d32b140be1cb4e975a34a65c17fc15d35c6d7"
   editor:
     fontFamily: "Menlo"
-    fontSize: 14
     preferredLineLength: 120
     confirmCheckoutHeadRevision: false
     invisibles:
       {}
   linter:
     lintOnChange: false
-    showErrorInline: false
+    statusBar: "Show error of the selected line"
+    showHighlighting: true
+    subtleLinterErrors: [
+      "rubocop"
+    ]
     showInfoMessages: true
-    statusBar: "Show error of the selected line"
-    showHighlighting: false
+    showErrorPanel: false
   "tree-view":
     hideIgnoredNames: true
     hideVcsIgnoredFiles: true
-  "linter-rubocop":
-    {}
-  "fuzzy-finder":
-    {}
+  "linter-rubocop": {}
+  "fuzzy-finder": {}
+  "autocomplete-plus": {}
   "one-dark-ui":
     layoutMode: "Compact"
+  tabs:
+    enableVcsColoring: true


Added Xcode installation to start of setup-macos.sh script

To prevent issues in relation to no Xcode CLI installed.

--- atom/config.cson
+++ atom/config.cson
@@ -22,6 +22,7 @@
     softWrap: true
   "exception-reporting":
     userId: "582938fb-a7d6-4690-93da-6e2f17aca978"
+  github: {}
   "line-ending-selector":
     defaultLineEnding: "LF"
   "linter-ui-default": {}


Remove setting for obsolete autocomplete package

--- atom/config.cson
+++ atom/config.cson
@@ -38,8 +38,6 @@
         column: 72
       }
     ]
-  autocomplete:
-    includeCompletionsFromAllBuffers: true
   "merge-conflicts":
     gitPath: "bin/git"
   "one-light-ui":


Resolve deprecation warning in scroll-cursor-to-top command

Warning:

  This method is deprecated on the model layer.
  Use TextEditorElement::pixelPositionForScreenPosition instead

  TextEditor.pixelPositionForScreenPosition - /Applications/Atom.app/Contents/Resources/app.asar/src/text-editor.js:2664:9
  ...

--- atom/init.coffee
+++ atom/init.coffee
@@ -68,10 +68,12 @@
   # Approximate Vim's "zt" motion: Scroll the view such that the line containing
   # the cursor is at the top of the view.
   'dot-atom:scroll-cursor-to-top': ->
-    editor = atom.workspace.getActiveTextEditor()
+    textEditor = atom.workspace.getActiveTextEditor()
+    textEditorElement = atom.views.getView(textEditor)
+    cursorPosition = textEditor.getCursorScreenPosition()
     pixelPositionForCursorPosition =
-      editor.pixelPositionForScreenPosition(editor.getCursorScreenPosition())
-    editor.setScrollTop(pixelPositionForCursorPosition.top)
+      textEditorElement.pixelPositionForScreenPosition(cursorPosition)
+    textEditor.setScrollTop(pixelPositionForCursorPosition.top)

   # Approximate Vim's "zz" motion: Scroll the view such that the line containing
   # the cursor is vertically centered in the view.


Add key binding for renaming files in tree view

--- atom/keymap.cson
+++ atom/keymap.cson
@@ -118,3 +118,9 @@
   'alt-cmd-b': 'find-and-replace:toggle-whole-word-option'
 '.platform-darwin .project-find':
   'alt-cmd-b': 'project-find:toggle-whole-word-option'
+
+# Tree View sidebar (when focused)
+'.tree-view':
+
+  # Rename the selected file or directory
+  'cmd-e': 'tree-view:rename'


Use ctrl-j/k to move down/up between sections in Git dock

--- atom/keymap.cson
+++ atom/keymap.cson
@@ -6,6 +6,13 @@

   'ctrl-|': 'pane:split-right-and-copy-active-item'
   'ctrl--': 'pane:split-down-and-copy-active-item'
+
+'.github-StagingView':
+  'ctrl-j': 'core:focus-next'
+  'ctrl-k': 'core:focus-previous'
+
+'.github-CommitView-editor atom-text-editor:not([mini])':
+  'ctrl-k': 'core:focus-previous'

 'atom-workspace atom-text-editor:not(.mini)':
   'ctrl-a': 'editor:move-to-beginning-of-line'


Rescale to original page on review step.

--- overrides.coffee
+++ overrides.coffee
@@ -77,17 +77,17 @@

   rect_index = parseInt (subjectViewer.taskIndex - INITIAL_STEPS) / ANNOTATION_STEPS

-  if rectangles.length > 0
+  if task.key in ['illustration', 'review']
+    subjectViewer.rescale()
+    ms.rescale(0,0,subjectViewer.maxWidth,subjectViewer.maxHeight)
+
+  if task.key in ['details', 'parts'] and rectangles.length > 0
     current_tool = rectangles[rect_index]
     w = current_tool.mark.width + MARGIN * 2
     h = current_tool.mark.height + MARGIN * 2
     ms.svg.attr 'width', w
     ms.svg.attr 'height', h
     ms.rescale current_tool.mark.left - MARGIN, current_tool.mark.top - MARGIN, w, h
-
-  if task.key is 'illustration'
-    subjectViewer.rescale()
-    ms.rescale(0,0,subjectViewer.maxWidth,subjectViewer.maxHeight)

   if task.key is 'parts'
     LAST_TASK = rect_index == rectangles.length - 1


refactor some duplicated code Move checking pinpoints against rectangles into its own function.

--- overrides.coffee
+++ overrides.coffee
@@ -139,29 +139,25 @@
   current_tool?.el.classList.add 'selected'

 moving = false
+update_current_tool = (mark) ->
+  return unless mark.inside?
+  current_tool?.el.classList.remove 'selected'
+  for tool in ms.tools when tool.mark._taskIndex is 1
+    rectangle = tool.mark
+    if mark.inside rectangle
+      current_tool = tool
+      current_tool?.el.classList.add 'selected'
+
 ms.on 'marking-surface:change', (mark) ->
   unless moving
     moving = true
     setTimeout ->
-      if mark?._taskIndex is 2
-        current_tool?.el.classList.remove 'selected'
-
-        for tool in ms.tools when tool.mark._taskIndex is 1
-          rectangle = tool.mark
-          if mark.inside rectangle
-            current_tool = tool
-            current_tool?.el.classList.add 'selected'
+      update_current_tool mark
       moving = false
     , 500

 ms.addEvent 'marking-surface:tool:select', ({detail}) ->
-  pinpoint = detail[0] if detail[0].mark._taskIndex is 2
-  current_tool?.el.classList.remove 'selected' if pinpoint?
-  for tool in ms.tools when tool.mark._taskIndex is 1
-    rectangle = tool.mark
-    if pinpoint?.mark.inside rectangle
-      current_tool = tool
-      current_tool?.el.classList.add 'selected'
+  update_current_tool detail[0].mark

 ms.on 'marking-surface:add-tool', (tool) ->
   {label} = decisionTree.currentTask.getChoice() ? ''


Set BrowserWindow's background color to white to re-enable sub-pixel AA

In Electron 0.37.3 the default background color has changed from white
to transparent, and as per the sub pixel anti-aliasing specification,
the layer which hosts the glyphs that need to be rendered needs to have
an opaque background.

We have also observed that upon reload everything seems to work smoothly
even without this fix: this probably points in the direction of a
regression in Electron, but in the meantime this fix should suffice to
allow us to proceed.

--- src/browser/atom-window.coffee
+++ src/browser/atom-window.coffee
@@ -24,6 +24,7 @@
     options =
       show: false
       title: 'Atom'
+      backgroundColor: "#fff"
       webPreferences:
         backgroundThrottling: not @isSpec


Update mycha.bin.coffee

Update mycha.bin.coffee

--- lib/mycha.bin.coffee
+++ lib/mycha.bin.coffee
@@ -4,7 +4,7 @@
   help: "Shows this help page"

 usage_text = """
-                Usage: mycha <action> [options]
+                Usage: mycha <command> [options]

                 where <command> is one of:
                 #{("  #{command_name} - #{command_desc}" for command_name, command_desc of commands).join("\n")}


* use @Class instead of @contructor

* use @Class instead of @contructor

--- src/custom-factory.coffee
+++ src/custom-factory.coffee
@@ -219,15 +219,15 @@

     if not flatOnly
-      @::register= (aClass, aOptions)-> @constructor.register aClass, aOptions
-      @::unregister= (aName)-> @constructor.unregister aName
+      @::register= (aClass, aOptions)-> @Class.register aClass, aOptions
+      @::unregister= (aName)-> @Class.unregister aName
       @::registered= (aName)-> Factory(aName)
       @::registeredClass= (aName)->
         aName = Factory.formatName aName
-        result = @constructor[aName]
+        result = @Class[aName]
         return result if result
         aName  = Factory.getRealNameFromAlias aName
-        return @constructor[aName] if aName
+        return @Class[aName] if aName
         return
       @::path = (aRootName)->
         '/' + @pathArray(aRootName).join '/'


fix unable to show file warning

fix unable to show file warning

--- lib/tree-view.coffee
+++ lib/tree-view.coffee
@@ -572,8 +572,10 @@
   showSelectedEntryInFileManager: ->
     return unless filePath = @selectedEntry()?.getPath()

-    unless shell.showItemInFolder(filePath)
+    return unless fs.existsSync(filePath)
       atom.notifications.addWarning("Unable to show #{filePath} in #{@getFileManagerName()}")
+
+    shell.showItemInFolder(filePath)

   showCurrentFileInFileManager: ->
     return unless filePath = atom.workspace.getCenter().getActiveTextEditor()?.getPath()


Remove obsolete utilities.isFile()

Remove obsolete utilities.isFile()

--- lib/utilities.coffee
+++ lib/utilities.coffee
@@ -22,8 +22,6 @@
   arr.regexp ?= new RegExp('((' + a.join(')|(') + '))$')

 module.exports=
-  # is the given name that of a regular file?
-  isFile: isFile
   # does a file with one of the given extensions exist?
   hasAnExtension: hasAnExtension


change to call removeStatusBarClass once, if-else -> switch

--- lib/vim-state.coffee
+++ lib/vim-state.coffee
@@ -484,14 +484,10 @@
         atom.workspaceView.statusBar?.prependRight("
Command
")
         @updateStatusBar()

-    if @mode is "insert"
-      @removeStatusBarClass()
-      $('#status-bar-vim-mode').addClass('status-bar-vim-mode-insert').html("Insert")
-    else if @mode is "command"
-      @removeStatusBarClass()
-      $('#status-bar-vim-mode').addClass('status-bar-vim-mode-command').html("Command")
-    else if @mode is "visual"
-      @removeStatusBarClass()
-      $('#status-bar-vim-mode').addClass('status-bar-vim-mode-visual').html("Visual")
+    @removeStatusBarClass()
+    switch @mode
+      when 'insert'  then $('#status-bar-vim-mode').addClass('status-bar-vim-mode-insert').html("Insert")
+      when 'command' then $('#status-bar-vim-mode').addClass('status-bar-vim-mode-command').html("Command")
+      when 'visual'  then $('#status-bar-vim-mode').addClass('status-bar-vim-mode-visual').html("Visual")

   removeStatusBarClass: -> $('#status-bar-vim-mode').removeClass('status-bar-vim-mode-insert status-bar-vim-mode-command status-bar-vim-mode-visual')


Use when check in App#dispose

Use when check in App#dispose

--- src/chaplin/application.coffee
+++ src/chaplin/application.coffee
@@ -56,8 +56,8 @@
       return if @disposed

       properties = ['dispatcher', 'layout', 'router']
-      for prop in properties
-        this[prop]?.dispose()
+      for prop in properties when this[prop]?
+        this[prop].dispose()
         delete this[prop]

       @disposed = true


Destroy the tether on destroy

Destroy the tether on destroy

--- coffee/drop.coffee
+++ coffee/drop.coffee
@@ -261,11 +261,14 @@
     destroy: ->
       @remove()

+      @tether?.destroy()
+
       for {element, event, handler} in @_boundEvents
         element.removeEventListener event, handler

       @_boundEvents = []

+      @tether = null
       @drop = null
       @content = null
       @target = null


Rename forgotten variable to flake8

Rename forgotten variable to flake8

--- lib/linter/flake8.coffee
+++ lib/linter/flake8.coffee
@@ -51,10 +51,10 @@
   constructCommand: ->
     command = []

-    userRubocopPath = atom.config.get('atom-lint.flake8.path')
+    userFlake8Path = atom.config.get('atom-lint.flake8.path')

-    if userRubocopPath?
-      command.push(userRubocopPath)
+    if userFlake8Path?
+      command.push(userFlake8Path)
     else
       command.push('flake8')


:lipstick: Use ? to guard against falsy panelsByName

--- lib/settings-view.coffee
+++ lib/settings-view.coffee
@@ -65,7 +65,7 @@
   getActivePanelName: -> @state.get('activePanelName')

   showPanel: (name) ->
-    if @panelsByName and @panelsByName[name]
+    if @panelsByName?[name]
       @panels.children().hide()
       @panelMenu.children('.active').removeClass('active')
       @panelsByName[name].show()


Displays Nodes and Sensors

Displays Nodes and Sensors

--- prototypes/model_maps/coffeescripts/load_map.coffee
+++ prototypes/model_maps/coffeescripts/load_map.coffee
@@ -3,58 +3,49 @@
       xml_text = $("#scenario_text").val()
       xml = $.parseXML(xml_text)
       window.textarea_scenario = window.aurora.Scenario.from_xml($(xml).children())
-      window.main_stuff.display_marks()
+      window.main_stuff.display()

 	myOptions =
-    center: new google.maps.LatLng(37.85298358928589, -122.29984760284424)
+    center: new google.maps.LatLng(37, -122)
     zoom: 14
     mapTypeId: google.maps.MapTypeId.ROADMAP
     mapTypeControl: false
     zoomControl: true
     zoomControlOptions: {
       style: google.maps.ZoomControlStyle.DEFAULT,
-      position: google.maps.ControlPosition.TOP_LEFT
-    }
-
-  window.map = new google.maps.Map(document.getElementById("map_canvas"),myOptions)
+	    position: google.maps.ControlPosition.TOP_LEFT
+     }
+   window.map = new google.maps.Map(document.getElementById("map_canvas"),myOptions)

-  # drawingManager = new google.maps.drawing.DrawingManager(
-  #         drawingMode: google.maps.drawing.OverlayType.MARKER
-  #         drawingControl: true
-  #
-  #         drawingControlOptions:
-  #           position: google.maps.ControlPosition.TOP_CENTER
-  #           drawingModes: [
-  #             google.maps.drawing.OverlayType.MARKER,
-  #             google.maps.drawing.OverlayType.CIRCLE
-  #           ]
-  #
-  #         markerOptions:
-  #           icon: "data/img/dot.png"
-  #
-  #         circleOptions:
-  #           fillColor: "#ffff00"
-  #           fillOpacity: 1
-  #           strokeWeight: 5
-  #           clickable: false
-  #           zIndex: 1
-  #           editable: true
-  #
-  #       )
-  #
-  #       drawingManager.setMap(window.map)
-  #
-window.main_stuff.display_marks = ->
-  node = {}
-  node1 = {}
-  node.lat = window.textarea_scenario.get('network').get('nodelist').get('node')[0].get('position').get('point')[0].get('lat')
-  node.lng = window.textarea_scenario.get('network').get('nodelist').get('node')[0].get('position').get('point')[0].get('lng')
-  node1.lat = window.textarea_scenario.get('network').get('nodelist').get('node')[1].get('position').get('point')[0].get('lat')
-  node1.lng = window.textarea_scenario.get('network').get('nodelist').get('node')[1].get('position').get('point')[0].get('lng')
-  window.map.setCenter(new google.maps.LatLng(node.lat, node.lng))
-  marker = getMarker(new google.maps.LatLng(node.lat, node.lng))
-  marker1 = getMarker(new google.maps.LatLng(node1.lat, node1.lng))
-  getPolyLine(marker,marker1)
+window.main_stuff.display = ->
+	network = window.textarea_scenario.get('network')
+	window.map.setCenter(new google.maps.LatLng(getLat(network), getLng(network)))
+	drawNodes(network.get('nodelist').get('node'))
+	drawSensors(network.get('sensorlist').get('sensor'))
+	drawLinks(network.get('linklist').get('link'))
+  # node.lat = window.textarea_scenario.get('network').get('nodelist').get('node')[0].get('position').get('point')[0].get('lat')
+  # node.lng = window.textarea_scenario.get('network').get('nodelist').get('node')[0].get('position').get('point')[0].get('lng')
+  # node1.lat = window.textarea_scenario.get('network').get('nodelist').get('node')[1].get('position').get('point')[0].get('lat')
+  # node1.lng = window.textarea_scenario.get('network').get('nodelist').get('node')[1].get('position').get('point')[0].get('lng')
+  # window.map.setCenter(new google.maps.LatLng(node.lat, node.lng))
+  # marker = getMarker(new google.maps.LatLng(node.lat, node.lng))
+  # marker1 = getMarker(new google.maps.LatLng(node1.lat, node1.lng))
+  # getPolyLine(marker,marker1)
+
+drawNodes = (nodes) ->
+	for node in nodes
+			getMarker(node,"Node")
+
+drawSensors = (sensors) ->
+	for sensor in sensors
+			getMarker(sensor,"Sensor")
+
+getLat = (elem) ->
+		elem.get('position').get('point')[0].get('lat')
+
+getLng = (elem) ->
+		elem.get('position').get('point')[0].get('lng')
+

 getPolyLine = (marker1, marker2) ->
 	new google.maps.Polyline({
@@ -65,19 +56,27 @@
 			map: window.map
 	  });

-getMarker = (pos) ->
+getMarker = (elem,type) ->
 	new google.maps.Marker({
-					position: pos,
+					position: new google.maps.LatLng(getLat(elem), getLng(elem)),
 					map: window.map,
 					draggable: true,
 					clickable: true,
 					hasShadow: false,
-					icon: getMarkerImage(),
-					tooltip: "Hello"
+					icon: getMarkerImage(elem,type),
+					title: type + ": " + elem.get('name') + "\nLatitude: " + roundDec(getLat(elem),4) + "\nLongitude: " + roundDec(getLng(elem),4)
 		  	});

-getMarkerImage = ->
-	new google.maps.MarkerImage('data/img/dot.png',
+getMarkerImage = (elem,type) ->
+	switch type
+		when "Sensor" then img = "camera-orig"
+		when "Node"
+			img = if elem.get('type') != "T" then "dot" else "square"
+
+	new google.maps.MarkerImage('data/img/' + img + '.png',
 				      new google.maps.Size(32, 32),
 				      new google.maps.Point(0,0),
 				      new google.maps.Point(16, 16));
+
+roundDec = (num,dec) ->
+		Math.round(num * Math.pow(10,dec)) / Math.pow(10,dec)


webapp: markdown button in project/new and changing tex file icon to the one for latex (instead of excel?)

--- src/smc-webapp/project_new.cjsx
+++ src/smc-webapp/project_new.cjsx
@@ -174,9 +174,13 @@
             </Row>
             <Row style={row_style}>
                 <Col sm=6>
-                    <Tip title='LaTeX Document'   icon='file-excel-o'
+                    <Tip title='Markdown File'   icon='cc-icon-markdown'
+                        tip='Create a Markdown formatted document with real-time preview.'>
+                        <NewFileButton icon='cc-icon-markdown' name='Markdown' on_click={@props.create_file} ext='md' />
+                    </Tip>
+                    <Tip title='LaTeX Document'   icon='cc-icon-tex-file'
                         tip='Create a professional quality technical paper that contains sophisticated mathematical formulas.'>
-                        <NewFileButton icon='file-excel-o' name='LaTeX Document' on_click={@props.create_file} ext='tex' />
+                        <NewFileButton icon='cc-icon-tex-file' name='LaTeX' on_click={@props.create_file} ext='tex' />
                     </Tip>
                     <Tip title='Terminal'  icon='terminal'
                         tip="Create a command line terminal.  CoCalc includes a full interactive Linux command line console and color xterm.  Run command line software, vim, emacs and more.">


exprt _internals for testing

exprt _internals for testing

--- src/smc-webapp/smc-react.coffee
+++ src/smc-webapp/smc-react.coffee
@@ -592,7 +592,7 @@
 if DEBUG
     smc?.redux = redux  # for convenience in the browser (mainly for debugging)

-_internals =
+exports._internals =
         AppRedux                 : AppRedux
         harvest_import_functions : harvest_import_functions
         harvest_own_functions    : harvest_own_functions


fix bug introduced in issue #2518

fix bug introduced in issue #2518

--- src/smc-webapp/smc-react.coffee
+++ src/smc-webapp/smc-react.coffee
@@ -22,6 +22,10 @@
 #
 # Question: can we use redux to implement the same API as r.cjsx exports (which was built on Flummox).
 ###############################################################################
+
+# Important: code below now assumes that a global variable called "DEBUG" is **defined**!
+if not DEBUG?
+    DEBUG = false

 {EventEmitter}   = require('events')
 async            = require('async')
@@ -583,7 +587,7 @@
 exports.Store    = Store
 exports.ReactDOM = require('react-dom')

-if DEBUG? and DEBUG
+if DEBUG
     smc?.redux = redux  # for convenience in the browser (mainly for debugging)

 _internals =


(fix) safari debug only for OS X

(fix) safari debug only for OS X

--- src/steroids/SafariDebug.coffee
+++ src/steroids/SafariDebug.coffee
@@ -31,6 +31,10 @@

   listViews: ()=>
     new Promise (resolve, reject) =>
+      unless steroidsCli.host.os.isOSX()
+        reject new steroidsCli.PlatformError
+        return
+
       getViews = if steroidsCli.host.os.osx.isYosemite()
         @runJavaScript "yosemite-safari.js", ["safari", "listviews"]
       else
@@ -43,6 +47,10 @@

   open: (argument) =>
     new Promise (resolve, reject) =>
+      unless steroidsCli.host.os.isOSX()
+        reject new steroidsCli.PlatformError
+        return
+
       @runAppleScript("openSafariDevMenu.scpt", argument).then ->
         resolve()
       .catch (error) ->


Fix #6 Set localhost for brunch watch

Fix #6 Set localhost for brunch watch

--- brunch-config.coffee
+++ brunch-config.coffee
@@ -21,4 +21,4 @@
       sourceMaps: true

   server:
-    hostname: '192.168.0.22'
+    hostname: '127.0.0.1'


canvas with features and user selection. fixes performance problems

Former-commit-id: db9f478dc17e94b71e5ae57fd266d2bc00167a5a [formerly 68f00398f4dc50c9d45be1a25bbefdbd61adc080]
Former-commit-id: 0f844d4f841ee291af80d4d7df32bdd46364ab45

--- src/views/CanvasSeqBlock.coffee
+++ src/views/CanvasSeqBlock.coffee
@@ -50,9 +50,17 @@
     for i in [0.. @model.length - 1] by 1
       continue if @model.at(i).get('hidden')
       @drawSeq {model: @model.at(i), y: y}
-      #view = new SeqView {model: @model.at(i), g: @g}
       y = y + rectHeight
-
+      # out of viewport - stop
+      if y > @el.height
+        break
+
+    y = - @g.zoomer.get "_alignmentScrollTop"
+    # draw again - overlays
+    for i in [0.. @model.length - 1] by 1
+      continue if @model.at(i).get('hidden')
+      @drawSeqExtended {model: @model.at(i), y: y}
+      y = y + rectHeight
       # out of viewport - stop
       if y > @el.height
         break
@@ -64,14 +72,10 @@
     rectHeight = @g.zoomer.get "rowHeight"

     @ctx.font="14px Courier New"
-    #@ctx.font="14px Lucida Console"
-
-    x = - @g.zoomer.get "_alignmentScrollLeft"
+
     # skip unneeded blocks at the beginning
-    start = Math.max 0, - Math.ceil(x / rectWidth)
-    x = 0
-
-    selection = @_getSelection data.model
+    start = Math.max 0, Math.abs(Math.ceil( - @g.zoomer.get('_alignmentScrollLeft') / rectWidth))
+    x = - Math.abs( - @g.zoomer.get('_alignmentScrollLeft') % rectWidth)

     for j in [start.. seq.length - 1] by 1
       c = seq[j]
@@ -82,18 +86,38 @@
         @ctx.fillRect x,y,rectWidth,rectHeight
         @ctx.strokeText c,x + 3,y + 12,rectWidth

-      # check for selection
-      if selection.indexOf(j) >= 0
-        @ctx.lineWidth = 2
-        @ctx.fillStyle = "red"
-        @ctx.strokeRect x,y,rectWidth,rectHeight
-        @ctx.lineWidth = 1
-
       x = x + rectWidth
-
       # out of viewport - stop
       if x > @el.width
         break
+
+  drawSeqExtended: (data) ->
+    seq = data.model.get "seq"
+    rectWidth = @g.zoomer.get "columnWidth"
+    rectHeight = @g.zoomer.get "rowHeight"
+
+    start = Math.max 0, Math.abs(Math.ceil( - @g.zoomer.get('_alignmentScrollLeft') / rectWidth))
+    x = - Math.abs( - @g.zoomer.get('_alignmentScrollLeft') % rectWidth)
+    xZero = x - start * rectWidth
+
+    selection = @_getSelection data.model
+    [mPrevSel,mNextSel] = @_getPrevNextSelection data.model
+    features = data.model.get "features"
+
+    yZero = Math.ceil(data.y / rectHeight) * rectHeight
+
+    for j in [start.. seq.length - 1] by 1
+      starts = features.startOn j
+      if starts.length > 0
+        for f in starts
+          @appendFeature f: f,xZero: x, yZero: yZero
+
+      x = x + rectWidth
+      # out of viewport - stop
+      if x > @el.width
+        break
+
+    @_appendSelection model: data.model, xZero: xZero, yZero: yZero

   _onclick: (e) ->
     coords = mouse.getMouseCoords e
@@ -146,6 +170,111 @@

     return selection

+  appendFeature: (data) ->
+    f = data.f
+    # TODO: this is a very naive way of using SVG to display features
+    boxWidth = @g.zoomer.get("columnWidth")
+    boxHeight = @g.zoomer.get("rowHeight")
+    width = (f.get("xEnd") - f.get("xStart")) * boxWidth
+
+    beforeWidth = @ctx.lineWidth
+    @ctx.lineWidth = 3
+    beforeStyle = @ctx.strokeStyle
+    @ctx.strokeStyle = f.get "fillColor"
+
+
+    @ctx.strokeRect data.xZero, data.yZero, width,boxHeight
+    @ctx.strokeStyle = beforeStyle
+    @ctx.lineWidth = beforeWidth
+
+  # displays the current user selection
+  # and checks the prev and next row for selection  -> no borders in a selection
+  _renderSelection: (data) ->
+
+    xZero = data.xZero
+    yZero = data.yZero
+    n = data.n
+    selection = data.selection
+    mPrevSel= data.mPrevSel
+    mNextSel = data.mNextSel
+
+    # get the length of this selection
+    selectionLength = 0
+    for i in [n.. data.model.get("seq").length - 1] by 1
+
+      if selection.indexOf(i) >= 0
+        selectionLength++
+      else
+        break
+
+    # TODO: ugly!
+    boxWidth = @g.zoomer.get("columnWidth")
+    boxHeight = @g.zoomer.get("rowHeight")
+    totalWidth = (boxWidth * selectionLength) + 1
+
+    hidden = @g.columns.get('hidden')
+
+    @ctx.beginPath()
+    beforeWidth = @ctx.lineWidth
+    @ctx.lineWidth = 3
+    beforeStyle = @ctx.strokeStyle
+    @ctx.strokeStyle = "#FF0000"
+
+    xZero += n * boxWidth
+
+    # split up the selection into single cells
+    xPart = 0
+    for i in [0.. selectionLength - 1]
+      xPos = n + i
+      if hidden.indexOf(xPos) >= 0
+        continue
+      # upper line
+      unless mPrevSel? and mPrevSel.indexOf(xPos) >= 0
+        @ctx.moveTo xZero + xPart, yZero
+        @ctx.lineTo xPart + boxWidth + xZero, yZero
+      # lower line
+      unless mNextSel? and mNextSel.indexOf(xPos) >= 0
+        @ctx.moveTo xPart + xZero, boxHeight + yZero
+        @ctx.lineTo xPart + boxWidth + xZero, boxHeight + yZero
+
+      xPart += boxWidth
+
+    # left
+    @ctx.moveTo xZero,yZero
+    @ctx.lineTo xZero, boxHeight + yZero
+
+    # right
+    @ctx.moveTo xZero + totalWidth,yZero
+    @ctx.lineTo xZero + totalWidth, boxHeight + yZero
+
+    @ctx.stroke()
+    @ctx.strokeStyle = beforeStyle
+    @ctx.lineWidth = beforeWidth
+
+  _appendSelection: (data) ->
+    seq = data.model.get("seq")
+    selection = @_getSelection data.model
+    hidden = @g.columns.get "hidden"
+    # get the status of the upper and lower row
+    [mPrevSel,mNextSel] = @_getPrevNextSelection data.model
+
+    boxWidth = @g.zoomer.get("columnWidth")
+    boxHeight = @g.zoomer.get("rowHeight")
+
+    # avoid unnecessary loops
+    return if selection.length is 0
+
+    hiddenOffset = 0
+    for n in [0..seq.length - 1] by 1
+      if hidden.indexOf(n) >= 0
+        hiddenOffset++
+      else
+        k = n - hiddenOffset
+        # only if its a new selection
+        if selection.indexOf(n) >= 0 and (k is 0 or selection.indexOf(n - 1) < 0 )
+          @_renderSelection n:n,selection: selection,mPrevSel: mPrevSel,mNextSel:mNextSel, xZero: data.xZero, yZero: data.yZero, model: data.model
+
+

   _adjustWidth: ->
     if @el.parentNode?
@@ -161,3 +290,15 @@
       @g.zoomer.set "alignmentWidth", @maxWidth
     @el.style.width = Math.min calcWidth, @maxWidth
     @el.setAttribute 'width', @el.style.width
+
+  _getPrevNextSelection: (model) ->
+    # looks at the selection of the prev and next el
+    # TODO: this is very naive, as there might be gaps above or below
+
+    modelPrev = model.collection.prev model
+    modelNext = model.collection.next model
+    mPrevSel = @_getSelection modelPrev if modelPrev?
+    mNextSel = @_getSelection modelNext if modelNext?
+    [mPrevSel,mNextSel]
+
+


Compare the repo path to the conflict set.

--- lib/merge-conflicts-view.coffee
+++ lib/merge-conflicts-view.coffee
@@ -105,9 +105,8 @@
   @markConflictsIn: (conflicts, editorView) ->
     return unless conflicts

-    editor = editorView.getEditor()
-    p = editor.getPath()
-    rel = path.relative atom.project.getPath(), p
-    return unless _.contains(conflicts, rel)
+    fullPath = editorView.getEditor().getPath()
+    repoPath = atom.project.getRepo().relativize fullPath
+    return unless _.contains(conflicts, repoPath)

     new ConflictMarker(editorView)


If we're been destroyed then we won't have an async anymore.

--- src/git-repository.coffee
+++ src/git-repository.coffee
@@ -493,7 +493,7 @@
   refreshStatus: ->
     asyncRefresh = @async.refreshStatus().then =>
       @statusesByPath = {}
-      @branch = @async.branch
+      @branch = @async?.branch

     syncRefresh = new Promise (resolve, reject) =>
       @handlerPath ?= require.resolve('./repository-status-handler')


Add failer notification

Add failer notification

--- lib/rubocop-auto-correct.coffee
+++ lib/rubocop-auto-correct.coffee
@@ -17,7 +17,10 @@
     basename = path.basename(filePath)
     command = atom.config.get('rubocop-auto-correct.rubocopCommandPath')
     args = ['-a', filePath]
-    exit = (code) -> atom.notifications.addSuccess("#{command} -a #{basename}")
+    exit = (code) -> if (code == 0)
+      atom.notifications.addSuccess("#{command} -a #{basename}")
+    else
+      atom.notifications.addError("Failer #{command} -a #{basename}")
     process = new BufferedProcess({command, args, exit})
     process


Remove semi

Remove semi

--- lib/rubocop-auto-correct.coffee
+++ lib/rubocop-auto-correct.coffee
@@ -121,7 +121,7 @@

   autoCorrectFile: (editor)  ->
     filePath = editor.getPath()
-    buffer = editor.getBuffer();
+    buffer = editor.getBuffer()

     rubocopCommand = @rubocopCommand()
     command = rubocopCommand[0]


UI tweaks

UI tweaks

--- lib/spark-ide-login-view.coffee
+++ lib/spark-ide-login-view.coffee
@@ -38,8 +38,6 @@

     @loginPromise = null

-    # TODO: Enter to submit
-
     # As Atom doesn't provide password input, we have to hack EditorView to act as one
     #
     # Known issues:
@@ -67,6 +65,8 @@
         else
           @passwordEditor.originalText = _s.splice(@passwordEditor.originalText, cursor.column - 1, 1)
         @passwordEditor.backspace
+      else if e.which == 13
+        @login()
       true

   # Returns an object that can be retrieved when package is activated
@@ -83,6 +83,7 @@
       @passwordEditor.getEditor().setText ''
       @passwordEditor.originalText = ''
       @errorLabel.hide()
+      @emailEditor.focus()

   hide: ->
     if @hasParent()


fixes 2655

fixes 2655

--- app/pages/project/stats/stats.cjsx
+++ app/pages/project/stats/stats.cjsx
@@ -56,28 +56,33 @@
     @props.handleWorkflowChange(@props.type, event)

   render: ->
-    if @props.range?
-      range = []
-      for r in @props.range.split(',')
-        if r
-          range.push(parseInt(r, 10))
-        else
-          range.push(undefined)
+    if @state.statData?
+      if @props.range?
+        range = []
+        for r in @props.range.split(',')
+          if r
+            range.push(parseInt(r, 10))
+          else
+            range.push(undefined)
+      else
+        range = [undefined, undefined]
+      workflowSelect = @workflowSelect()
+

+        {@props.type[0].toUpperCase() + @props.type.substring(1)}s per{' '}
+        <select value={@props.by} onChange={@handleGraphChange.bind this, @props.type}>
+          <option value="hour">hour</option>
+          <option value="day">day</option>
+          <option value="week">week</option>
+          <option value="month">month</option>
+        </select>
+        {workflowSelect}
+

+        {<Graph data={@state.statData} by={@props.by} range={range} num={24} handleRangeChange={@handleRangeChange} />}
+

     else
-      range = [undefined, undefined]
-    workflowSelect = @workflowSelect()
-

-      {@props.type[0].toUpperCase() + @props.type.substring(1)}s per{' '}
-      <select value={@props.by} onChange={@handleGraphChange.bind this, @props.type}>
-        <option value="hour">hour</option>
-        <option value="day">day</option>
-        <option value="week">week</option>
-        <option value="month">month</option>
-      </select>
-      {workflowSelect}
-

-      {<Graph data={@state.statData} by={@props.by} range={range} num={24} handleRangeChange={@handleRangeChange} /> if @state.statData?}
-

+

+        There is no stats data available at this time.
+

   handleGraphChange: (which, e) ->
     @props.handleGraphChange(which, e)


Delete subject if an error occurred in creation steps

--- app/partials/subject-uploader.cjsx
+++ app/partials/subject-uploader.cjsx
@@ -77,6 +77,8 @@
             uploads: @state.uploads.concat success
             batch: @state.batch.concat subject
         .catch (error) =>
+          subject.delete()
+
           @setState
             errors: @state.errors.concat error
         .then =>


添加完成issue的方法，外部需要调用

添加完成issue的方法，外部需要调用

--- biz/issue.coffee
+++ biz/issue.coffee
@@ -12,6 +12,15 @@
   constructor: ()->
     @schema = _schema
     super
+
+  #完成某个issue，将会触发一系列操作，比如说发邮件等
+  finishedIssue: (id, cb)->
+    data =
+      id: id
+      status: 'done'
+      finish_time: Number(new Date())
+    #暂时只做保存，并不做其它处理
+    @save data, cb

   #重载save
   save: (data, callback)->
@@ -40,7 +49,7 @@
     }

     #修改状态
-    this.save data, (err)->
+    super data, (err)->
       res.end()


Wrap RangeTracker in try catch

Wrap RangeTracker in try catch

--- app/coffee/RangesManager.coffee
+++ app/coffee/RangesManager.coffee
@@ -13,7 +13,10 @@
 			if !!update.meta.tc
 				rangesTracker.setIdSeed(update.meta.tc)
 			for op in update.op
-				rangesTracker.applyOp(op, { user_id: update.meta?.user_id })
+				try
+					rangesTracker.applyOp(op, { user_id: update.meta?.user_id })
+				catch error
+					return callback(error)

 		if rangesTracker.changes?.length > RangesManager.MAX_CHANGES or rangesTracker.comments?.length > RangesManager.MAX_COMMENTS
 			return callback new Error("too many comments or tracked changes")


make private.json optional

make private.json optional

--- app/templates/_Gruntfile.coffee
+++ app/templates/_Gruntfile.coffee
@@ -1,7 +1,7 @@
 module.exports = (grunt) ->

 	pkg = grunt.file.readJSON('package.json')
-	pvt = grunt.file.readJSON('private.json')
+	pvt = grunt.file.readJSON('private.json') if grunt.file.exists('private.json')

 	#*------------------------------------*\
 	#   $LOAD DEPENDENCIES


updated gruntfile

updated gruntfile

--- app/templates/_gruntfile.coffee
+++ app/templates/_gruntfile.coffee
@@ -28,7 +28,7 @@
       coffee:
         files: ["<%%= yeoman.app %>/scripts/{,*/}*.coffee"]
         tasks: [
-          "coffee:dist",
+          "coffee:dist"
           "concat:server"
         ]

@@ -42,8 +42,8 @@
       sass:
         files: ["<%%= yeoman.app %>/styles/{,*/}*.{scss,sass}"]
         tasks: [
-          "sass:server",
-          "autoprefixer",
+          "sass:server"
+          "autoprefixer"
           "copy:server"
         ]

@@ -119,10 +119,10 @@
           lineNumbers: true

         files: [{
-          expand: true,
-          cwd: '<%%= yeoman.app %>/styles',
-          src: ['*.{scss,sass}'],
-          dest: '.tmp/styles',
+          expand: true
+          cwd: '<%%= yeoman.app %>/styles'
+          src: ['*.{scss,sass}']
+          dest: '.tmp/styles'
           ext: '.css'
         }]

@@ -194,7 +194,7 @@
       dist:
         files: [
           expand: true
-          cwd: "<%%= yeoman.app %>/images"
+          cwd: "<%%= yeoman.dist %>/images"
           src: "{,*/}*.{png,jpg,jpeg}"
           dest: "<%%= yeoman.dist %>/images"
         ]
@@ -203,7 +203,7 @@
       dist:
         files: [
           expand: true
-          cwd: "<%%= yeoman.app %>/images"
+          cwd: "<%%= yeoman.dist %>/images"
           src: "{,*/}*.svg"
           dest: "<%%= yeoman.dist %>/images"
         ]
@@ -276,10 +276,8 @@
       ]

       dist: [
-        "coffee",
+        "coffee"
         "sass:dist"
-        "imagemin",
-        "svgmin"
       ]

@@ -288,22 +286,24 @@

   # $ grunt server
   grunt.registerTask "server", [
-    "clean",
-    "concurrent:server",
-    "concat:server",
-    "autoprefixer",
-    "copy",
+    "clean"
+    "concurrent:server"
+    "concat:server"
+    "autoprefixer"
+    "copy"
     "watch"
   ]

   # $ grunt build
   grunt.registerTask "build", [
-    "clean",
-    "concurrent:dist",
-    "uglify:dist",
-    "autoprefixer",
-    "copy",
-    "notify:dist",
+    "clean"
+    "concurrent:dist"
+    "uglify:dist"
+    "autoprefixer"
+    "copy"
+    "imagemin"
+    "svgmin"
+    "notify:dist"
   ]

   # $ grunt


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/function_extensions.coffee
+++ src/function_extensions.coffee
@@ -6,7 +6,7 @@
     return memo if ran
     ran = true

-    return memo = this.apply(this, arguments)
+    return memo = this.apply(arguments)

 Function::withBefore = (interception) ->
   method = this


Add d&d functionnality

Add d&d functionnality

--- BackofficeBundle/Resources/public/coffee/refreshUl.coffee
+++ BackofficeBundle/Resources/public/coffee/refreshUl.coffee
@@ -1,26 +1,31 @@
 stockedUl = null
 refreshUl = (ul) ->
-  childs = ul.children().not(".ui-sortable-helper")
+  childs = ul.children(':visible')
+  childOutFocus = childs.not(".ui-sortable-placeholder")
   nbrChildren = childs.length
-  direction = ((if nbrChildren > 0 and ul.width() is childs.first().width() then "height" else "width"))
+  direction = if childOutFocus.length > 0 and ul.width() is childOutFocus.first().width() then "height" else "width"
+  notdirection = if direction is "width" then "height" else "width"
   childs.each ->
     $(this).css direction, (100 / nbrChildren) + "%"
+    $(this).css notdirection, "100 %"
     return
   return
 makeSortable = (el) ->
   $("ul.ui-model-blocks", el).sortable(
     connectWith: "#content ul.ui-model-blocks",
     appendTo: 'body',
-    helper: 'clone',
+    helper: (event, obj) ->
+      $("

").addClass("ui-model").append obj.clone()
     start: (event, ui)->
       ui.placeholder.css "width", (100 * ui.item.width() / $(this).width()) + "%"
       ui.placeholder.css "height", (100 * ui.item.height() / $(this).height()) + "%"
+      stockedUl = ui.item.parent()
     change: (event, ui) ->
       refreshUl stockedUl  if stockedUl isnt null
       stockedUl = ui.placeholder.parent()
       refreshUl stockedUl
-      ui.helper.height ui.placeholder.height()
-      ui.helper.width ui.placeholder.width()
+      ui.helper.children().height ui.placeholder.height()
+      ui.helper.children().width ui.placeholder.width()
   ).disableSelection()
   return
 makeSortable ".blocks-panel"


Debugging

Debugging

--- lib/fold-functions.coffee
+++ lib/fold-functions.coffee
@@ -195,7 +195,9 @@
         'storage.type.arrow',
         'entity.name.function.constructor'
       )
-      @debugMessage('fold functions: is foldable', (foldable and isFunction and not isCommented))
+      @debugMessage('fold functions: is foldable', (foldable and isFunction and not isCommented), 'foldable', foldable, 'isFunction', isFunction, 'isCommented', isCommented)
+      if isFunction and not (foldable and isFunction and not isCommented)
+        @debugMessage('fold functions: line is a function, but cannot be folded', foldable, isCommented)
       if foldable and isFunction and not isCommented
         if @indentLevel == null
           @indentLevel = thisIndentLevel
@@ -216,10 +218,12 @@
   unfold: ->
     @fold('unfold')

+  # Log the scopes and copy them to the clipboard.
   scopes: ->
     editor = atom.workspace.getActiveTextEditor()
     position = editor.getCursorBufferPosition()
     scopes = @getScopesForBufferRow(editor, position.row)
+    atom.clipboard.write(list.join(', '))
     list = scopes.map (item) -> "* #{item}"
     content = "Scopes at Row\n#{list.join('\n')}"
     atom.notifications.addInfo(content, dismissable: true)


Fix warnings caused by invalid nesting of p elems

--- app/assets/javascripts/components/overview/inline_users.cjsx
+++ app/assets/javascripts/components/overview/inline_users.cjsx
@@ -22,7 +22,7 @@
     allowed = @props.role != 4 || (@props.current_user.role == 4 || @props.current_user.admin)
     button = <EnrollButton {...@props} users={@props.users} role={@props.role} key={key} inline=true allowed={allowed} show={@props.editable && allowed} />

-
{inline_list}{button}

+
{inline_list}{button}

 )

 module.exports = InlineUsers


Reset drawing tool history when the old data is loaded

[#77959140]

--- app/assets/javascripts/image_question_drawing_tool.js.coffee
+++ app/assets/javascripts/image_question_drawing_tool.js.coffee
@@ -203,7 +203,9 @@
     @set_drawing_tool_background()

   load_annotation: () ->
-    @drawing_tool.load(@$annotation_field.val())
+    @drawing_tool.load(@$annotation_field.val(), =>
+      @drawing_tool.resetHistory()
+    )

   is_annotation_data_correct: () ->
     reset_annotation_data = =>


Fix #217

Fix #217

--- lib/models/git-log.coffee
+++ lib/models/git-log.coffee
@@ -6,6 +6,7 @@
   atom.config.get('git-plus.amountOfCommitsToShow')

 gitLog = (repo, {onlyCurrentFile}={}) ->
+  currentFile = repo.relativize(atom.workspace.getActiveTextEditor()?.getPath())
   # opener doesn't get overwritten with a new instance of LogListView
   atom.workspace.addOpener (filePath) ->
     return new LogListView if filePath is ViewUriLog
@@ -14,7 +15,6 @@
     if view instanceof LogListView
       view.setRepo repo
       if onlyCurrentFile
-        currentFile = repo.relativize(atom.workspace.getActiveTextEditor()?.getPath())
         view.currentFileLog(onlyCurrentFile, currentFile)
       else
         view.branchLog()


default to -O2

default to -O2

--- lib/package/config.coffee
+++ lib/package/config.coffee
@@ -33,7 +33,7 @@
         description: 'Higher levels take longer to compile, but produce faster code.'
         type: 'integer'
         enum: [0, 1, 2, 3]
-        default: 3
+        default: 2
         radio: true
         order: 2
       deprecationWarnings:


refactor(productImport): Refactor filtering duplicate attributes

--- lib/product-import.coffee
+++ lib/product-import.coffee
@@ -300,8 +300,8 @@
             throw new Error(msg)
           else if @logOnDuplicateAttr
             @logger.warn msg
-          return false
-        return true
+        # filter out duplicate attributes
+        not isDuplicate

   _cleanDuplicateAttributes: (prodToProcess) ->
     prodToProcess.variants = prodToProcess.variants || []


pane close method

pane close method

--- lib/util/pane-item.coffee
+++ lib/util/pane-item.coffee
@@ -69,3 +69,6 @@
       atom.workspace.open "atom://ink-#{@constructor.name.toLowerCase()}/#{@id}", opts
     else
       throw new Error 'Pane does not have an ID'
+
+  close: ->
+    @currentPane()?.removeItem @


Don't reuse alerts

Don't reuse alerts

--- SingularityUI/app/controllers/TaskDetail.coffee
+++ SingularityUI/app/controllers/TaskDetail.coffee
@@ -133,7 +133,7 @@
                 delete @models.resourceUsage

     getAlerts: (taskHistory) =>
-        alerts = @models.task.get('alerts')
+        alerts = []
         # Is this a scheduled task that has been running much longer than previous ones?
         if @models.task.attributes.task.taskRequest.request.requestType == 'SCHEDULED' and @models.task.get('isStillRunning')
             times = taskHistory.models.map (t) =>
@@ -144,7 +144,7 @@
             avg = Math.round avg / times.length
             current =  new Date().getTime() - @models.task.get('task').taskId.startedAt
             # Alert if current uptime is longer than twice the average
-            if current > (avg * 2)
+            if current < (avg * 2)
                 alerts.push
                   title: 'Warning:',
                   message: 'This scheduled task has been running longer than twice the average for the request and may be stuck.',


ActivityTicker: Event listener is taken out of the settingMenu method. (bug)

--- client/Social/Activity/ContentDisplays/ActivityTicker.coffee
+++ client/Social/Activity/ContentDisplays/ActivityTicker.coffee
@@ -40,6 +40,11 @@
     group.on "PostIsCreated", @bound "addActivity"
     group.on "ReplyIsAdded", @bound "addComment"
     group.on "PostIsDeleted", @bound "deleteActivity"
+
+    @listController.listView.on 'ItemWasAdded', (view, index) =>
+      if view.data?
+        itemId = @getItemId view.data
+        @indexedItems[itemId] = view

     @load {}

@@ -72,11 +77,6 @@
           filterSelected ["user"]
     return menu

-    @listController.listView.on 'ItemWasAdded', (view, index) =>
-      if view.getData()?
-        itemId = @getItemId view.data
-        @itemsIndexed[itemId] = view
-
   getConstructorName :(obj)->
     if obj and obj.bongo_ and obj.bongo_.constructorName
       return obj.bongo_.constructorName


ActivityTicker: adjusting height to fill the screen

--- client/Social/Activity/ContentDisplays/ActivityTicker.coffee
+++ client/Social/Activity/ContentDisplays/ActivityTicker.coffee
@@ -49,6 +49,9 @@
         @indexedItems[itemId] = view

     @load {}
+
+    @once 'viewAppended', =>
+      @$('.kdscrollview').height window.innerHeight - 120

   settingsMenu:(data)->
     filterSelected = (filters=[]) =>


Fixed bug in seconds

Fixed bug in seconds

--- util/logger.coffee
+++ util/logger.coffee
@@ -28,7 +28,7 @@

     hours = now.getHours()
     minutes = now.getMinutes()
-    seconds = now.getMinutes()
+    seconds = now.getSeconds()
     milliseconds = now.getMilliseconds()
     time = @_pad(hours, 2) + ':' + @_pad(minutes, 2) + ':' + @_pad(seconds, 2) + '.' + @_pad(milliseconds, 3)


markdown display is now xss-safe

markdown display is now xss-safe

--- client/Framework/core/utils.coffee
+++ client/Framework/core/utils.coffee
@@ -110,8 +110,7 @@
           return text

     text = Encoder.htmlDecode text
-
-    marked text
+    marked Encoder.XSSEncode text

   applyLineBreaks: (text)->
     return null unless text


allow deletion in junk folder

allow deletion in junk folder

--- client/app/components/panel.coffee
+++ client/app/components/panel.coffee
@@ -114,9 +114,12 @@
         query.mailboxID = mailboxID

         # don't display conversations in Trash and Draft folders
-        isDraft = @state.selectedAccount?.get('draftMailbox') is mailboxID
-        isTrash = @state.selectedAccount?.get('trashMailbox') is mailboxID
-        if isDraft or isTrash
+        conversationDisabledBoxes = [
+            @state.selectedAccount?.get('draftMailbox')
+            @state.selectedAccount?.get('trashMailbox')
+            @state.selectedAccount?.get('junkMailbox')
+        ]
+        if mailboxID in conversationDisabledBoxes
             displayConversations = false
         else
             displayConversations = @state.settings.get 'displayConversation'


Fixed: the up/down in tasks

Fixed: the up/down in tasks

--- client/app/views/tasks_view.coffee
+++ client/app/views/tasks_view.coffee
@@ -44,14 +44,16 @@
     # Set focus on previous task. Preserve focus position.
     moveUpFocus: (taskLine, options) ->
         selector = "##{taskLine.model.id}"
-        nextDescription = taskLine.list.$(selector).prev().find(".description")
+        nextDescription = taskLine.list.$(selector).prev().prev()
+                            .find(".description")
         if nextDescription.length
             @moveFocus taskLine.descriptionField, nextDescription, options

     # Set focus on next task. Preserve focus position.
     moveDownFocus: (taskLine, options) ->
         selector = "##{taskLine.model.id}"
-        nextDescription = taskLine.list.$(selector).next().find(".description")
+        nextDescription = taskLine.list.$(selector).next().next()
+                            .find(".description")
         if nextDescription.length
             @moveFocus taskLine.descriptionField, nextDescription, options


Fix portamento by using setValueAtTime instead of just changing the value.

--- src/scripts/Syntho.coffee
+++ src/scripts/Syntho.coffee
@@ -23,7 +23,7 @@

 callback = (message, note) ->
     if note >= 0
-        vco1.frequency.value = freqMap.getFrequency(note)
+        vco1.frequency.setValueAtTime(freqMap.getFrequency(note), audioContext.currentTime)
         gate.gain.value = 1
     else
         gate.gain.value = 0


[MiniTask] fixed view templates

[MiniTask] fixed view templates

--- src/scripts/routes.coffee
+++ src/scripts/routes.coffee
@@ -3,11 +3,11 @@
   ($routeProvider) ->
     $routeProvider
     .when '/1',
-      templateUrl: '/views/visual1.html'
+      templateUrl: 'views/visual1.html'
       controller: 'tweetController'
     .when '/2',
-      templateUrl: '/views/visual2.html'
+      templateUrl: 'views/visual2.html'
     .when '/3/:query',
-      templateUrl: '/views/visual3.html'
+      templateUrl: 'views/visual3.html'
       controller: 'tweetListController'
 ])