:bug: New Helpers::validateMessages don't return

--- spec/helpers-spec.coffee
+++ spec/helpers-spec.coffee
@@ -10,7 +10,7 @@
     expect( -> Helpers.validateMessages(results)).toThrow()
   it "should return the results when validated.", ->
     results = [{type: 'Type'}]
-    expect(Helpers.validateMessages(results)).toEqual(results)
+    expect(Helpers.validateMessages(results)).toBeUndefined()

 describe "The Linter Validation Helper", ->
   it "should throw an exception when grammarScopes is not an Array.", ->


more tests for both directions of fFtT

more tests for both directions of fFtT

--- spec/motions-spec.coffee
+++ spec/motions-spec.coffee
@@ -1413,12 +1413,24 @@
       keydown('f')
       commandModeInputKeydown('a')
       expect(editor.getCursorScreenPosition()).toEqual [0, 0]
-      # bug was making this behaviour depend on the number
+      # a bug was making this behaviour depend on the count
       keydown('1')
       keydown('1')
       keydown('f')
       commandModeInputKeydown('a')
       expect(editor.getCursorScreenPosition()).toEqual [0, 0]
+      # and backwards now
+      editor.setCursorScreenPosition([0, 6])
+      keydown('1')
+      keydown('0')
+      keydown('F', shift: true)
+      commandModeInputKeydown('a')
+      expect(editor.getCursorScreenPosition()).toEqual [0, 6]
+      keydown('1')
+      keydown('1')
+      keydown('F', shift: true)
+      commandModeInputKeydown('a')
+      expect(editor.getCursorScreenPosition()).toEqual [0, 6]

     it "composes with d", ->
       editor.setCursorScreenPosition([0,3])
@@ -1468,11 +1480,24 @@
       keydown('t')
       commandModeInputKeydown('a')
       expect(editor.getCursorScreenPosition()).toEqual [0, 0]
+      # a bug was making this behaviour depend on the count
       keydown('1')
       keydown('1')
       keydown('t')
       commandModeInputKeydown('a')
       expect(editor.getCursorScreenPosition()).toEqual [0, 0]
+      # and backwards now
+      editor.setCursorScreenPosition([0, 6])
+      keydown('1')
+      keydown('0')
+      keydown('T', shift: true)
+      commandModeInputKeydown('a')
+      expect(editor.getCursorScreenPosition()).toEqual [0, 6]
+      keydown('1')
+      keydown('1')
+      keydown('T', shift: true)
+      commandModeInputKeydown('a')
+      expect(editor.getCursorScreenPosition()).toEqual [0, 6]

     it "composes with d", ->
       editor.setCursorScreenPosition([0,3])


Add tests for enter, checking equivalence to +

I added tests for enter too, for the heck of it. To avoid duplication, the tests merely compare the results of + and of enter and make sure that they are the same.

I could have tested equivalence of enter and + in more situations, but I think it’s likely that if they are the same in the situation tested, they will be the same anywhere. It would be an unlikely mistake for someone to change the keybindings of enter and + to different functions, and then edit only one of the functions in such a way that the results are only sometimes different.

I also changed some single quotes to double quotes for consistency.

--- spec/motions-spec.coffee
+++ spec/motions-spec.coffee
@@ -588,7 +588,7 @@
           keydown('d')
           keydown('-')

-        it 'deletes the current and previous line', ->
+        it "deletes the current and previous line", ->
           expect(editor.getText()).toBe "  abc\n"
           expect(editor.getCursorScreenPosition()).toEqual [0, 3]

@@ -626,7 +626,7 @@
           keydown('d')
           keydown('-')

-        it 'selects to the first character of the previous line', ->
+        it "selects to the first character of the previous line", ->
           expect(editor.getText()).toBe "abcdefg\n"
           expect(editor.getCursorScreenPosition()).toEqual [0, 0]

@@ -650,11 +650,11 @@
           keydown('3')
           keydown('-')

-        it 'deletes the current line plus that many previous lines', ->
+        it "deletes the current line plus that many previous lines", ->
           expect(editor.getText()).toBe "1\n6\n"
           expect(editor.getCursorScreenPosition()).toEqual [1, 0]

-  describe "the + keybinding (which should also be equivalent to the enter keybinding)", ->
+  describe "the + keybinding", ->
     beforeEach ->
       editor.setText("  abc\n  abc\nabcdefg\n")

@@ -673,7 +673,7 @@
           keydown('d')
           keydown('+')

-        it 'deletes the current and next line', ->
+        it "deletes the current and next line", ->
           expect(editor.getText()).toBe "  abc\n"
           expect(editor.getCursorScreenPosition()).toEqual [0, 3]

@@ -711,7 +711,7 @@
           keydown('d')
           keydown('+')

-        it 'selects to the first character of the next line', ->
+        it "selects to the first character of the next line", ->
           expect(editor.getText()).toBe "abcdefg\n"
           expect(editor.getCursorScreenPosition()).toEqual [0, 0]

@@ -735,9 +735,46 @@
           keydown('3')
           keydown('+')

-        it 'deletes the current line plus that many following lines', ->
+        it "deletes the current line plus that many following lines", ->
           expect(editor.getText()).toBe "1\n6\n"
           expect(editor.getCursorScreenPosition()).toEqual [1, 0]
+
+  describe "the enter keybinding", ->
+    keydownCodeForEnter = '\r' # 'enter' does not work
+    startingText = "  abc\n  abc\nabcdefg\n"
+
+    describe "from the middle of a line", ->
+      startingCursorPosition = [1, 3]
+
+      describe "as a motion", ->
+        it "acts the same as the + keybinding", ->
+          # do it with + and save the results
+          editor.setText(startingText)
+          editor.setCursorScreenPosition(startingCursorPosition)
+          keydown('+')
+          referenceCursorPosition = editor.getCursorScreenPosition()
+          # do it again with enter and compare the results
+          editor.setText(startingText)
+          editor.setCursorScreenPosition(startingCursorPosition)
+          keydown(keydownCodeForEnter)
+          expect(editor.getCursorScreenPosition()).toEqual referenceCursorPosition
+
+      describe "as a selection", ->
+        it "acts the same as the + keybinding", ->
+          # do it with + and save the results
+          editor.setText(startingText)
+          editor.setCursorScreenPosition(startingCursorPosition)
+          keydown('d')
+          keydown('+')
+          referenceText = editor.getText()
+          referenceCursorPosition = editor.getCursorScreenPosition()
+          # do it again with enter and compare the results
+          editor.setText(startingText)
+          editor.setCursorScreenPosition(startingCursorPosition)
+          keydown('d')
+          keydown(keydownCodeForEnter)
+          expect(editor.getText()).toEqual referenceText
+          expect(editor.getCursorScreenPosition()).toEqual referenceCursorPosition

   describe "the gg keybinding", ->
     beforeEach ->


fix #11

fix #11

--- grails-app/assets/javascripts/chat.coffee
+++ grails-app/assets/javascripts/chat.coffee
@@ -99,6 +99,7 @@
     stompClient.send "/app/updateUser", {}, JSON.stringify(message)
     stompClient.send "/app/log", {}, JSON.stringify(message)
     $('#chatMessage').focus()
+    $("a[title='Go to today']").click()

 # send chat message


change scale

change scale

--- graphics/001-terrain-gen/generator.coffee
+++ graphics/001-terrain-gen/generator.coffee
@@ -12,7 +12,7 @@
         seeds = terrain.seeds()
         evolve = ->
             seeds = terrain.gen(seeds)
-            if seeds[0] == 8192
+            if seeds[0] == 4096
                 self.postMessage({msg: 'done!'})
                 clearInterval(handle)
             else


http.parsers unstable... clarified

http.parsers unstable... clarified

--- lib/devices/Device.coffee
+++ lib/devices/Device.coffee
@@ -122,8 +122,8 @@

   # Parse SSDP headers using the HTTP module parser.
   parseRequest: (msg, rinfo, cb) ->
-    # `http.parsers` is not documented and not guaranteed to be stable.
-    parser = http.parsers.alloc()
+    HTTPParser = process.binding('http_parser').HTTPParser
+    parser = new HTTPParser 'request'
     parser.reinitialize 'request'
     parser.socket = {}
     parser.onIncoming = (req) ->


Made gulp.dirs enumerable

Made gulp.dirs enumerable

--- wrapper.coffee
+++ wrapper.coffee
@@ -45,10 +45,18 @@

 Object.defineProperty wrapper, 'dirs',
   value: Object.create Object.prototype,
-    source: value: options.source
-    test:   value: options.test
-    build:  value: options.build
-    dist:   value: options.dist
+    source:
+      value: options.source
+      enumerable: true
+    test:
+      value: options.test
+      enumerable: true
+    build:
+      value: options.build
+      enumerable: true
+    dist:
+      value: options.dist
+      enumerable: true

 Object.defineProperty wrapper, 'main',
   value: path.resolve (bundle.main ? (wrapper.dirs.source + '/' + 'main.js'))


Folding scopes update

Folding scopes update

--- lib/fold-functions.coffee
+++ lib/fold-functions.coffee
@@ -48,7 +48,6 @@

     if atom.config.get('fold-functions.autofold')
       atom.workspace.observeTextEditors (editor) =>
-        # editor.displayBuffer.tokenizedBuffer.onDidTokenize => @autofold(editor)
         editor.tokenizedBuffer.onDidTokenize => @autofold(editor)
         # editor.onDidChangeGrammar => @autofold(editor)

@@ -206,7 +205,8 @@
         'meta.function',
         'meta.method',
         'storage.type.arrow',
-        'entity.name.function.constructor'
+        'entity.name.function',
+        'support.function',
       )
       @debugMessage 'fold functions: is foldable',
         lines,


Completed error handling

Completed error handling

--- src/wordnet.coffee
+++ src/wordnet.coffee
@@ -149,7 +149,7 @@
         if err?
           reject err
         else
-          resolve(data)
+          resolve data

   findSense: (input, callback) ->
@@ -184,7 +184,11 @@
   findSenseAsync: (input) ->
     wordnet = @
     new Promise (resolve, reject) ->
-      wordnet.findSense input, (data) -> resolve(data)
+      wordnet.findSense input, (err, data) ->
+        if err?
+          reject err
+        else
+          resolve data

   querySense: (input, callback) ->
@@ -217,7 +221,11 @@
   querySenseAsync: (input) ->
     wordnet = @
     new Promise (resolve, reject) ->
-      wordnet.querySense input, (data) -> resolve(data)
+      wordnet.querySense input, (err, data) ->
+        if err?
+          reject err
+        else
+          resolve data

   lookupFromFiles: (files, results, word, callback) ->


coffeelint

coffeelint

--- src/workout.coffee
+++ src/workout.coffee
@@ -1,5 +1,5 @@
-Set = require("./set")
-actions = require("./actions")
+Set = require('./set')
+actions = require('./actions')

 units =
   yards: 1
@@ -31,10 +31,10 @@
     currentSet

   totalDistance: ->
-    actions.sum @sets, "totalDistance"
+    actions.sum @sets, 'totalDistance'

   totalTime: ->
-    actions.sum @sets, "totalTime"
+    actions.sum @sets, 'totalTime'

   totalIntervals: ->
     total = 0


destructuring; start checking hooks

destructuring; start checking hooks

--- src/util/index.coffee
+++ src/util/index.coffee
@@ -6,13 +6,23 @@
   ArgumentError: class ArgumentError extends Error

   check_mix: (mixin, options) ->
-    {omit} = options
+    {omit, hooks} = options
+
     if omit?
       if (!Array.isArray(omit) || !omit)
         throw new MixinUtils.ArgumentError "Expected omit option to be a nonempty Array"
-      diff = _.difference(omit, _.keys(mixin))
+      diff = _.difference(omit, mixin.mixin_keys)
       if diff.length
         throw new MixinUtils.ArgumentError "Some omit keys aren't in mixin object: #{diff}"
+
+    if hooks?
+      if !_.isObject(hooks) || Array.isArray(hooks) || _.isFunction(hooks)
+        # http://underscorejs.org/#isObject
+        throw new MixinUtils.ArgumentError "Expected object literal for hooks option"
+      diff = _.difference(Object.keys(hooks), mixin.mixin_keys)
+      if diff.length
+        throw new MixinUtils.ArgumentError "Some hooks keys aren't in mixin object: #{diff}"
+


:lipstick:

:lipstick:

--- src/app/git.coffee
+++ src/app/git.coffee
@@ -16,7 +16,8 @@
     if repoPath
       repoPath.substring(0, repoPath.length - 5)

-  getHead: -> @repo.getHead() || ''
+  getHead: ->
+    @repo.getHead() or ''

   isIgnored: (path) ->
     path and @repo.isIgnored(path)


Update Bumped.plugins.coffee

Update Bumped.plugins.coffee

--- lib/Bumped.plugins.coffee
+++ lib/Bumped.plugins.coffee
@@ -29,6 +29,5 @@
       plugin @bumped, settings, next
     , cb

-  isEmpty: (plugins) ->
-    return true unless plugins
+  isEmpty: (plugins = []) ->
     Object.keys(plugins).length is 0


Machine: fixed language and some code styling

--- client/Main/providers/machine.coffee
+++ client/Main/providers/machine.coffee
@@ -2,17 +2,17 @@

   @State = {

-    "NotInitialized"  # Initial state, machine instance does not exists
-    "Building"        # Build started machine instance creating...
-    "Starting"        # Machine is booting...
-    "Running"         # Machine is physically running
-    "Stopping"        # Machine is turning off...
-    "Stopped"         # Machine is turned off
-    "Rebooting"       # Machine is rebooting...
-    "Terminating"     # Machine is getting destroyed...
-    "Terminated"      # Machine is destroyed, not exists anymore
-    "Unknown"         # Machine is in an unknown state
-                      # needs to solved manually
+    'NotInitialized'  # Initial state, machine instance does not exists
+    'Building'        # Build started machine instance is being created...
+    'Starting'        # Machine is booting...
+    'Running'         # Machine is physically running
+    'Stopping'        # Machine is turning off...
+    'Stopped'         # Machine is turned off
+    'Rebooting'       # Machine is rebooting...
+    'Terminating'     # Machine is being destroyed...
+    'Terminated'      # Machine is destroyed, does not exist anymore
+    'Unknown'         # Machine is in an unknown state
+                      # needs to be resolved manually
   }


--- client/code/view/tool/content.coffee
+++ client/code/view/tool/content.coffee
@@ -7,6 +7,10 @@

   render: ->
     $('body').addClass('fullscreen')
+    # work out correct position for fullscreen iframe, accounting for injected lastpass bars etc
+    $c = $('#content')
+    @$el.css 'top', $c.offset().top - $c.outerHeight(true) - $c.innerHeight()
+    @

   showContent: ->
     @boxUrl = @model.endpoint()


bool write cmd

bool write cmd

--- lib/Repl/ReplClass.coffee
+++ lib/Repl/ReplClass.coffee
@@ -7,14 +7,15 @@
 module.exports =
 class Repl

-    processCmd:()->
+    processCmd:(write_cmd)->
       #console.log('2')
       if(@processing)
         @retour(@prompt)
       if(@cmdQueue.length > 0)
         @processing = true
         cmd = @cmdQueue.shift()
-        @print += cmd
+        if write_cmd = true
+            @print += cmd
         @replProcess.stdin.write(cmd)
         console.log(@endSequence)
         if cmd.slice(-@endSequence.length) != @endSequence
@@ -42,12 +43,12 @@
     closeRepl:(code) ->
       console.log('child process exited with code ' + code)

-    writeInRepl:(cmd) ->
+    writeInRepl:(cmd, write_cmd) ->
       #console.log(s)
       #@replProcess.stdin.write(s)
       @cmdQueue.push(cmd)
       if(!@processing)
-        @processCmd()
+        @processCmd(write_cmd)

     constructor:(r_format, @retour) ->
       self = this


Fixed: selecting a calendar didn't work in the popover

--- client/app/views/widgets/combobox.coffee
+++ client/app/views/widgets/combobox.coffee
@@ -49,12 +49,14 @@
         @$el.removeClass 'expanded' unless @$el.is ':focus'

     onBlur: =>
+        console.log 'blur'
         @$el.removeClass 'expanded' unless @menuOpen
         @trigger 'edition-complete', @value()

     onSelect: (ev, ui) =>
         @$el.blur().removeClass 'expanded'
         @updateBadge ev, ui
+        @trigger 'edition-complete', ui.item.value

     updateBadge: (ev, ui) =>
         @badge?.remove()


Fix camera tracking when no longer tracking an object

Setting a fixed point to the previous position of whatever was being tracked
lets the camera catch up and slow to a halt.

--- app/js/world.coffee
+++ app/js/world.coffee
@@ -40,11 +40,15 @@
   removeParticle: (particle) -> _.without(@particles, particle)
   removeAllParticles: -> @particles = []

-  track: (@tracking) ->
-    if @tracking
+  track: (target) ->
+    if target
+      @tracking = target
       @camera1 = @camera2 = @tracking.position
     else
-      @camera1 = @camera2 = @center()
+      if @tracking
+        @tracking = {position: @tracking.position}
+      else
+        @camera1 = @camera2 = @center()

   center: -> [@sizeX/2, @sizeY/2]

@@ -111,9 +115,12 @@

       delta = Vec.sub @center(), @camera2
       for body in @bodies
+        continue if body is @tracking
         body.position = Vec.add body.position, delta
       for particle in @particles
+        continue if body is @tracking
         particle.position = Vec.add particle.position, delta
+      @tracking.position = Vec.add @tracking.position, delta
       @camera1 = Vec.add @camera1, delta
       @camera2 = Vec.add @camera2, delta
       @cameraDelta = delta
@@ -293,7 +300,7 @@

   update: (dt) ->
     super dt
-    @updateStarfield @cameraDelta
+    @updateStarfield @cameraDelta if Vec.magnitudeSquared(@cameraDelta) > 0

   draw: =>
     for stars in @starfield


Fixed me emitting 'me:synced' and anything listening for that.

--- app/lib/auth.coffee
+++ app/lib/auth.coffee
@@ -12,7 +12,7 @@
     me.set 'testGroupNumber', Math.floor(Math.random() * 256)
     me.patch()

-  Backbone.listenTo(me, 'sync', Backbone.Mediator.publish('me:synced', {me: me}))
+  Backbone.listenTo(me, 'sync', -> Backbone.Mediator.publish('me:synced', {me: me}))

 module.exports.createUser = (userObject, failure=backboneFailure, nextURL=null) ->
   user = new User(userObject)


Bind history only to the Alt key

Bind history only to the Alt key

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -415,13 +415,13 @@
                     @hide_suggestion_and_description()
                     return true
                 # Catching history navigation
-                else if event.type is 'keyup' and (event.ctrlKey or event.metaKey or event.altKey) and event.which is 38 # Key up
+                else if event.type is 'keyup' and event.altKey and event.which is 38 # Key up
                     if @history_displayed_id < @history.length
                         @history_displayed_id++
                         @codemirror.setValue @history[@history.length-@history_displayed_id]
                         event.preventDefault()
                         return true
-                else if event.type is 'keyup' and (event.ctrlKey or event.metaKey or event.altKey) and event.which is 40 # Key down
+                else if event.type is 'keyup' and event.altKey and event.which is 40 # Key down
                     if @history_displayed_id > 1
                         @history_displayed_id--
                         @codemirror.setValue @history[@history.length-@history_displayed_id]
@@ -431,12 +431,12 @@
                         @history_displayed_id--
                         @codemirror.setValue @draft
                         @codemirror.setCursor @codemirror.lineCount(), 0 # We hit the draft and put the cursor at the end
-                else if event.type is 'keyup' and (event.ctrlKey or event.metaKey or event.altKey) and event.which is 33 # Page up
+                else if event.type is 'keyup' and event.altKey and event.which is 33 # Page up
                     @history_displayed_id = @history.length
                     @codemirror.setValue @history[@history.length-@history_displayed_id]
                     event.preventDefault()
                     return true
-                else if event.type is 'keyup' and (event.ctrlKey or event.metaKey or event.altKey) and event.which is 34 # Page down
+                else if event.type is 'keyup' and event.altKey and event.which is 34 # Page down
                     @history_displayed_id = @history.length
                     @codemirror.setValue @history[@history.length-@history_displayed_id]
                     @codemirror.setCursor @codemirror.lineCount(), 0 # We hit the draft and put the cursor at the end
@@ -473,7 +473,7 @@
             # 0 is for firefox
             if not event? or (event.which isnt 37 and event.which isnt 38 and event.which isnt 39 and event.which isnt 40 and event.which isnt 33 and event.which isnt 34 and event.which isnt 35 and event.which isnt 36 and event.which isnt 0)
                 @history_displayed_id = 0
-                @draft = @codemirror.getValue()a
+                @draft = @codemirror.getValue()

             if event?.which isnt 9 # has to be before create_suggestion
                 @cursor_for_auto_completion = @codemirror.getCursor()


Added "merge" function

Added "merge" function

--- index.coffee
+++ index.coffee
@@ -167,3 +167,44 @@
       fn = null

 @once = once
+
+
+###
+  @merge
+###
+
+merge = (a, b)->
+  if a is b
+    return a
+  if a is null or typeof a is 'undefined'
+    return b
+  if b is null or typeof b is 'undefined'
+    return a
+  if (typeof a) is 'object'
+    if (typeof b) isnt 'object'
+      return a
+    else
+      if Array.isArray a
+        if Array.isArray b
+          return [].concat a, b
+        else
+          return a
+      else
+        if Array.isArray b
+          return a
+        else
+          c = {}
+          for own key, _ of a
+            if b.hasOwnProperty key
+              c[key] = merge a[key], b[key]
+            else
+              c[key] = a[key]
+          for own key, _ of b
+            unless c.hasOwnProperty key
+              c[key] = b[key]
+          return c
+  return a
+
+@merge = merge
+
+


Keep last query in memory

Keep last query in memory

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -11,6 +11,8 @@
         alert_reconnection_success_template: Handlebars.compile $('#alert-reconnection_success-template').html()
         databases_suggestions_template: Handlebars.compile $('#dataexplorer-databases_suggestions-template').html()
         namespaces_suggestions_template: Handlebars.compile $('#dataexplorer-namespaces_suggestions-template').html()
+
+        saved_query: '' # Last value @codemirror.getValue()

         events:
             'click .CodeMirror': 'handle_keypress'
@@ -351,6 +353,7 @@

         # Make suggestions when the user is writing
         handle_keypress: (editor, event) =>
+            DataExplorerView.Container.prototype.saved_query = @codemirror.getValue()
             saved_cursor = @codemirror.getCursor()
             if event?.which?
                 # If the user hit tab, we switch the highlighted suggestion
@@ -1000,6 +1003,7 @@
                 matchBrackets: true

             @codemirror.setSize '100%', 'auto'
+            @codemirror.setValue @saved_query

         handle_gutter_click: (editor, line) =>
             start =


Support non service worker platforms.

Support non service worker platforms.

--- index.coffee
+++ index.coffee
@@ -1,4 +1,4 @@
-navigator.serviceWorker.register 'worker.js'
+navigator.serviceWorker.register 'worker.js' if navigator.serviceWorker

 # localForage = require('localforage').default
 md5 = require('sparkmd5').ArrayBuffer.hash


Handle unexpected token -- Related to #1334

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -2595,15 +2595,27 @@
             @driver_connected = 'connected'

         # Called if the driver could not connect
-        error_on_connect: =>
-            @results_view.cursor_timed_out()
-            # We fail to connect, so we display a message except if we were already disconnected and we are not trying to manually reconnect
-            # So if the user fails to reconnect after a failure, the alert will still flash
-            @.$('#user-alert-space').hide()
-            @.$('#user-alert-space').html @alert_connection_fail_template({})
-            @.$('#user-alert-space').slideDown 'fast'
-            @reconnecting = false
-            @driver_connected = 'error'
+        error_on_connect: (error) =>
+            if /^(Unexpected token)/.test(error.message)
+                # Unexpected token, the server couldn't parse the protobuf message
+                # The trugh is we don't know which query failed (unexpected token), but it seems safe to suppose in 99% that the last one failed.
+                @.$('.loading_query_img').hide()
+                @results_view.render_error(null, error)
+
+                # We save the query since the callback will never be called.
+                @save_query
+                    query: @raw_query
+                    broken_query: true
+
+            else
+                @results_view.cursor_timed_out()
+                # We fail to connect, so we display a message except if we were already disconnected and we are not trying to manually reconnect
+                # So if the user fails to reconnect after a failure, the alert will still flash
+                @$('#user-alert-space').hide()
+                @$('#user-alert-space').html @alert_connection_fail_template({})
+                @$('#user-alert-space').slideDown 'fast'
+                @reconnecting = false
+                @driver_connected = 'error'

         # Reconnect, function triggered if the user click on reconnect
         reconnect: (event) =>
@@ -3509,7 +3521,7 @@
             try
                 r.connect @server, (err, connection) ->
                     if err?
-                        that.on_fail()
+                        that.on_fail(err)
                     else
                         that.connection = connection
                         that.on_success(connection)
@@ -3518,7 +3530,7 @@
                 unless @dont_timeout_connection
                     @timeout = setTimeout @connect, 5*60*1000
             catch err
-                @on_fail()
+                @on_fail(err)

         postpone_reconnection: =>
             clearTimeout @timeout


Use config for directory paths

Use config for directory paths

--- index.coffee
+++ index.coffee
@@ -11,12 +11,12 @@

     done = @async()

-    {appDirectory, outputDirectory} = @options()
+    {appDirectory, outputDirectory} = grunt.config(@name)
     outputDirectory ?= path.resolve('.')

     metadata = grunt.file.readJSON('package.json')

-    inputTemplate = grunt.file.read(path.join(__dirname, 'template.nuspec'))
+    inputTemplate = grunt.file.read(path.resolve(__dirname, '..', 'template.nuspec'))
     nuspecContent = _.template(inputTemplate, metadata)

     targetNuspecPath = path.join(temp.mkdirSync('squirrel-installer-'), "#{metadata.name}.nuspec")
@@ -32,7 +32,4 @@
       outputDirectory
     ]

-    console.log cmd
-    console.log args
-
     grunt.util.spawn({cmd, args}, done)


Updated index.coffee (_generateReadStream)

--- index.coffee
+++ index.coffee
@@ -18,9 +18,21 @@
     @bytesPerHundredMilisecond = Math.floor (@bytesPerSecond / 10)
     @carryBytes = @bytesPerSecond - (10 * @bytesPerHundredMilisecond)

+  _generateReadStream: ->
+    @readStream = fslib.createReadStream @inputFilePath
+
+    @readStream.on 'error', (err)=>
+      @emit 'error', err
+
+    @readStream.on 'end', =>
+      @writeStream.end()
+      @emit 'end'
+
   copy: ->
     @_gatherInputFileStats =>
       @_calculateUpperLimits()

+      @_generateReadStream()
+

 @Slowcopy = Slowcopy


The token is now a synchronous process.

The token is now a synchronous process.

--- index.coffee
+++ index.coffee
@@ -36,7 +36,7 @@
         done err, results?.repo

   github.register 'token',
-    update: (done) ->
+    update: ->
       process.env.IMPROMPTU_GITHUB_TOKEN

   requestGitHub = (path, options, done) ->


Act on the last intersection observer entry

--- index.coffee
+++ index.coffee
@@ -128,7 +128,7 @@

 		# Handle state changes.  There should only ever be one entry and we're
 		# destructuring the properties we care about since they have long names.
-		updateInViewport: ([{
+		updateInViewport: ([..., {
 				boundingClientRect: target,
 				rootBounds: root
 			}]) ->


make sure stuff stays synchrnous in perftest

--- performance/PerformanceTestCases.coffee
+++ performance/PerformanceTestCases.coffee
@@ -1,5 +1,6 @@
 _ = require "lodash"
 Bacon = require "../dist/Bacon"
+runId = 0

 class Generator
   constructor: ->
@@ -24,8 +25,9 @@
   everyNth: (n, stream) ->
     stream.filter (x) -> x % n == 0
   withGenerator: (fun, rounds=100) ->
+    myRunId = ++runId
     gen = f.generator()
-    fun(gen).onValue((v) -> )
+    fun(gen).onValue((v) -> console.error("async result detected!") if myRunId != runId)
     gen.ticks(rounds)
   combineTemplate: (gen, width, depth) ->
     if depth == 0


Don't expect marker to provide a buffer

Don't expect marker to provide a buffer

This is no longer the case now that the marker returned is from telepath.

--- lib/bookmarks-view.coffee
+++ lib/bookmarks-view.coffee
@@ -8,7 +8,7 @@
 class BookmarksView extends SelectList
   @viewClass: -> "#{super} bookmarks-view overlay from-top"

-  filterKey: 'bookmarkFilterText'
+  filterKey: 'filterText'

   initialize: ->
     super
@@ -22,7 +22,7 @@

   getFilterText: (bookmark) ->
     segments = []
-    bookmarkRow = bookmark.getStartPosition().row
+    bookmarkRow = bookmark.marker.getStartPosition().row
     segments.push(bookmarkRow)
     if bufferPath = bookmark.buffer.getPath()
       segments.push(bufferPath)
@@ -31,19 +31,20 @@
     segments.join(' ')

   getLineText: (bookmark) ->
-    bookmark.buffer.lineForRow(bookmark.getStartPosition().row)?.trim()
+    bookmark.buffer.lineForRow(bookmark.marker.getStartPosition().row)?.trim()

   populateBookmarks: ->
-    markers = []
+    bookmarks = []
     attributes = class: 'bookmark'
     for buffer in project.getBuffers()
       for marker in buffer.findMarkers(attributes)
-        marker.bookmarkFilterText = @getFilterText(marker)
-        markers.push(marker)
-    @setArray(markers)
+        bookmark = {marker, buffer}
+        bookmark.fitlerText = @getFilterText(bookmark)
+        bookmarks.push(bookmark)
+    @setArray(bookmarks)

   itemForElement: (bookmark) ->
-    bookmarkRow = bookmark.getStartPosition().row
+    bookmarkRow = bookmark.marker.getStartPosition().row
     if filePath = bookmark.buffer.getPath()
       bookmarkLocation = "#{path.basename(filePath)}:#{bookmarkRow + 1}"
     else
@@ -68,7 +69,7 @@
   confirmed : (bookmark) ->
     for editor in rootView.getEditors()
       if editor.getBuffer() is bookmark.buffer
-        editor.activeEditSession.setSelectedBufferRange(bookmark.getRange(), autoscroll: true)
+        editor.activeEditSession.setSelectedBufferRange(bookmark.marker.getRange(), autoscroll: true)

   attach: ->
     super


Update mongo.coffee

Update mongo.coffee

Support for multiple mongo clustering hosts
Changes yml connection host structure to array

--- lib/services/mongo.coffee
+++ lib/services/mongo.coffee
@@ -15,7 +15,9 @@

   open: (connection = {}) ->
     promise = new Hope.Promise()
-    url = connection.host + ":" + connection.port + "/" + connection.db
+    for host, i in connection.host
+      connection.host[i] = host + ":" + connection.port
+    url = connection.host.toString() + "/" + connection.db
     if connection.user and connection.password
       url = connection.user + ":" + connection.password + "@" + url


update

update

--- app/assets/javascripts/common.js.coffee
+++ app/assets/javascripts/common.js.coffee
@@ -5,29 +5,16 @@
     if arr[i] < arr[tmp] then tmp = i
     i++
   return tmp
+

-widthIsOver1250 = () ->
-  col_heights = [30, 30, 30]
-  add_state = 0
-  $(".tlelm").each ->
-    article_height = $(this).height() + 90
-    article_half_width = $(this).width()/2
-    add_state = minIndex(col_heights)
-    ma_left = 0
-
-    if add_state is 0
-      ma_left = -400
-    else if add_state is 1
-      ma_left = 0
-    else
-      ma_left = 400
-
-    $(this).css({position:"absolute",top:col_heights[add_state],left:"50%","margin-left":ma_left-article_half_width})
-    col_heights[add_state] += article_height
-
-
-
-timeline_algorithm = (col_heights) ->
+timeline_algorithm = (width) ->
+  col_heights = []
+  i = 0
+  n = Math.round(width/400)
+  while(i < n)
+    col_heights.push(30)
+    i++
+
   add_state = 0

   $(".tlelm").each ->
@@ -37,7 +24,7 @@

     ma_left = 200

-    x = col_heights.length #2
+    x = col_heights.length
     a = 0.5
     b = -0.5
     y = a*x + b
@@ -48,7 +35,7 @@
     col_heights[add_state] += article_height

-widthIsUnder850 = () ->
+reset_timeline = () ->
   $(".tlelm").css({
     position:"relative",
     top:"",left:"",
@@ -61,12 +48,12 @@
     url = $(this).attr("src")
     $(this).attr("src",url+"?wmode=transparent")

-  if $(window).width() > 1250
-    timeline_algorithm([30,30,30])
-  else if $(window).width() > 850
-    timeline_algorithm([30,30])
+  w_width = $(window).width()
+
+  if w_width > 850
+    timeline_algorithm(w_width-200)
   else
-    timeline_algorithm([30])
+    reset_timeline()

 hidePostFormElms = (fadebool) ->
   if(typeof fadebool == 'undefined') then fadebool = true


Refresh all ratings on successful event reaction update.

Closes #7

--- app/assets/javascripts/custom.js.coffee
+++ app/assets/javascripts/custom.js.coffee
@@ -36,4 +36,6 @@
               type: 'PATCH',
               dataType: 'json',
               data: { event: { reaction: score } }
+              success: ->
+                refreshRatings()


Bezier: set the right height

Bezier: set the right height

The canvas was covering the secondary chart header

--- app/assets/javascripts/d3/bezier.coffee
+++ app/assets/javascripts/d3/bezier.coffee
@@ -8,7 +8,7 @@

     can_be_shown_as_table: -> true

-    outer_height: => @height + 20
+    outer_height: => @height + 60

     # This chart rendering is fairly complex. Here is the big picture:
     # this bezier chart is basically a stacked area chart. D3 provides some


Changed color of hydrogen import

Changed color of hydrogen import

--- app/assets/javascripts/d3/sankey.coffee
+++ app/assets/javascripts/d3/sankey.coffee
@@ -110,7 +110,7 @@
           {left: 'geo_ambient',                   right: 'hydrogen_prod',    gquery: 'geo_ambient_to_hydrogen_prod_in_sankey', color: '#787821'},
           {left: 'nuclear',                       right: 'hydrogen_prod',    gquery: 'nuclear_to_hydrogen_prod_in_sankey', color: '#ff7f0e'},
           {left: 'imported_electricity',          right: 'hydrogen_prod',    gquery: 'imported_electricity_to_hydrogen_prod_in_sankey', color: '#1f77b4'},
-          {left: 'imported_hydrogen',             right: 'hydrogen_prod',    gquery: 'hydrogen_import_to_hydrogen_prod_in_sankey', color: '#2ca02c'},
+          {left: 'imported_hydrogen',             right: 'hydrogen_prod',    gquery: 'hydrogen_import_to_hydrogen_prod_in_sankey', color: '#27d3d3'},
           {left: 'water',                         right: 'hydrogen_prod',    gquery: 'water_to_hydrogen_prod_in_sankey', color: '#4465c6'},

           {left: 'coal_and_derivatives',          right: 'central_heat_prod', gquery: 'coal_and_derivatives_to_central_heat_prod_in_sankey', color: '#252525'},


animation des boutons like

animation des boutons like

--- app/assets/javascripts/header.js.coffee
+++ app/assets/javascripts/header.js.coffee
@@ -49,4 +49,16 @@
   $('#likes > div').live('mouseleave', ->
     $(this).clearQueue()
     $(this).transition({opacity:0.1})
-  )+  )
+
+  # animation on like divs
+  animate_like_divs = ->
+    $('#likes .fb-like').clearQueue().transition({ opacity:0.93 }).transition({ opacity:0.1 }).delay(200, ->
+      $('#likes .twitter-like').clearQueue().transition({ opacity:0.93 }).transition({ opacity:0.1 }).delay(200, ->
+        $('#likes .gplus-like').clearQueue().transition({ opacity:0.93 }).transition({ opacity:0.1 }).delay(200, ->
+          setTimeout(animate_like_divs, 60000)
+        )
+      )
+    )
+
+  setTimeout(animate_like_divs, 10000)


Remove unfinshed sketch 'word-clouds' from sketch list

--- app/assets/javascripts/papers.js.coffee
+++ app/assets/javascripts/papers.js.coffee
@@ -6,8 +6,7 @@
 	"oxford": 0,
 	"fancy": 0,
 	"awl": 0,
-	"awl-details": 0,
-	"word-cloud": 0
+	"awl-details": 0
 }

 update_interval = 5000;


Guard against interval firing after editor is destroyed

--- src/editor-view.coffee
+++ src/editor-view.coffee
@@ -469,9 +469,10 @@
       $(document).off 'mousemove', moveHandler
       $(document).off 'mouseup', finalizeSelections

-      @editor.mergeIntersectingSelections(isReversed: @editor.getLastSelection().isReversed())
-      @editor.finalizeSelections()
-      @syncCursorAnimations()
+      unless @editor.isDestroyed()
+        @editor.mergeIntersectingSelections(isReversed: @editor.getLastSelection().isReversed())
+        @editor.finalizeSelections()
+        @syncCursorAnimations()

     moveHandler = (event = lastMoveEvent) =>
       return unless event?


Add METEOR_ACCOUNTS_LOCAL_UPDATE

Add METEOR_ACCOUNTS_LOCAL_UPDATE

--- src/AccountsLocal.coffee
+++ src/AccountsLocal.coffee
@@ -17,8 +17,10 @@
         email: env.METEOR_ADMIN_EMAIL
         roles: ['admin']
         # Admin password and email will always update on server restart based on the environment
-        # variables.
-        update: true
+        # variables. Set METEOR_ACCOUNTS_LOCAL_UPDATE to '0' to prevent updating existing users.
+        # This prevents needing to log in after each live reload in Meteor (due to the update
+        # changing the user).
+        update: if process.env.METEOR_ACCOUNTS_LOCAL_UPDATE == '0' then false else true

     # Create all other users in the user.json.
     _.each users, (userArgs, username) ->


Allow the 'default' key to have any type

--- src/Documentation.coffee
+++ src/Documentation.coffee
@@ -93,7 +93,8 @@

 renderProperty = (name, def, parent) ->
   throw new Error("Property #{name} is missing .description") if not def.description
-  throw new Error("Property #{name} is missing .type") if not def.type
+  unless parent.type is 'object' name is 'default'
+    throw new Error("Property #{name} is missing .type") if not def.type
   throw new Error("Parent schema not specified for #{name}") if not parent
   if parent.type == 'array'
     if not parent.items?.required?.length


Fix minor bug in TextFormatter.

Fix minor bug in TextFormatter.

--- lib/text-formatter.coffee
+++ lib/text-formatter.coffee
@@ -44,6 +44,7 @@
   handleNewline: (peek) ->
     indentLevel =
       @getIndentLevel() + @additionalIndent - (peek is '<DEDENT>')
+    console.assert indentLevel >= 0
     "\n#{makeIndents @getIndentChars(), indentLevel}"

   specialTokens:
@@ -55,7 +56,6 @@
       @getIndentChars()
     '<DEDENT>': ->
       @additionalIndent--
-      console.assert @additionalIndent >= 0
       # Nothing to output since handleNewline's already got this.
       ''


Removing stale FIXME comment.

Removing stale FIXME comment.

--- src/environment.coffee
+++ src/environment.coffee
@@ -18,8 +18,6 @@
             height : 600

         ((@[k] = v) for k, v of wolf.defaults(opts, defaults))
-
-        #FIXME: add arguments for gravity and whatnot.
         @logger = new wolf.Logger("wolf.Environment")
         @logger.debug("Initializing")


fix out-of-bounds for initValid

fix out-of-bounds for initValid

--- app/classifier/drawing-tools/point.cjsx
+++ app/classifier/drawing-tools/point.cjsx
@@ -28,13 +28,10 @@
     initMove: ({x, y}) ->
       {x, y}

-    initValid: (mark, {containerRect, scale}) ->
-      markRect =
-        left: containerRect.left + (mark.x * scale.vertical)
-        top: containerRect.top + (mark.y * scale.horizontal)
-        width: 1
-        height: 1
-      isInBounds markRect, containerRect
+    initValid: (mark, {naturalHeight, naturalWidth}) ->
+      notBeyondWidth = mark.x < naturalWidth
+      notBeyondHeight = mark.y < naturalHeight
+      notBeyondWidth and notBeyondHeight

     initRelease: ->
       _inProgress: false


Remove PromiseRenderer from inbox

Remove PromiseRenderer from inbox

--- app/talk/inbox.cjsx
+++ app/talk/inbox.cjsx
@@ -1,7 +1,6 @@
 React = require 'react'
 talkClient = require 'panoptes-client/lib/talk-client'
 apiClient = require 'panoptes-client/lib/api-client'
-PromiseRenderer = require '../components/promise-renderer'
 Paginator = require './lib/paginator'
 {Link} = require 'react-router'
 Loading = require '../components/loading-indicator'
@@ -15,6 +14,47 @@

 promptToSignIn = -> alert (resolve) -> <SignInPrompt onChoose={resolve} />

+ConversationLink = React.createClass
+  displayName: 'ConversationLink'
+
+  propTypes:
+    user: React.PropTypes.object
+    conversation: React.PropTypes.object
+
+  getInitialState: ->
+    users: []
+    messages: []
+
+  componentWillMount: ->
+    apiClient
+      .type 'users'
+      .get @props.conversation.links.users.filter (userId) => userId isnt @props.user.id
+      .then (users) =>
+        @setState {users}
+
+    @props.conversation
+      .get 'messages', {page_size: 1, sort: '-created_at'}
+      .then (messages) =>
+        @setState {messages}
+
+  render: ->
+    unread = @props.conversation.is_unread
+

+

+        {@state.users.map (user, i) =>
+

+            <Link key={user.id} to="/users/#{user.login}">{user.display_name}</Link>
+
{timeAgo(@state.messages[0]?.updated_at)}{', ' if i isnt (@state.users.length-1)}

+
}
+

+
+      <Link to="/inbox/#{@props.conversation.id}">
+        {if unread
+          }
+        {@props.conversation.title}
+      </Link>
+

+
 module.exports = React.createClass
   displayName: 'TalkInbox'

@@ -24,76 +64,68 @@
   getDefaultProps: ->
     location: query: page: 1

+  getInitialState: ->
+    conversations: []
+
+  componentWillMount: ->
+    @setConversations @props.user
+
   componentWillReceiveProps: (nextProps) ->
+    unless nextProps.user is @props.user
+      @setConversations(nextProps.user)
     unless nextProps.location.query.page is @props.location.query.page
-      @setConversations(nextProps.location.query.page)
+      @setConversations(nextProps.user, nextProps.location.query.page)

-  setConversations: (page) ->
+  setConversations: (user, page) ->
+    return unless user?
     conversationsQuery =
-      user_id: @props.user.id
+      user_id: user.id
       page_size: PAGE_SIZE
       page: @props.location.query.page
       sort: '-updated_at'
       include: 'users'

-    talkClient.type('conversations').get conversationsQuery
+    talkClient
+      .type 'conversations'
+      .get conversationsQuery
+      .then (conversations) =>
+        @setState {conversations}

   onPageChange: (page) ->
     @goToPage(page)

   goToPage: (n) ->
     @context.router.push "/inbox?page=#{n}"
-    @setConversations(n)
+    @setConversations(@props.user, n)

   message: (data, i) ->

{data.body}

-
-  conversationLink: (conversation, i) ->
-    unread = conversation.is_unread
-

-      <PromiseRenderer promise={apiClient.type('users').get(conversation.links.users.filter (userId) => userId isnt @props.user.id)}>{(users) =>
-

-          {users.map (user, i) =>
-

-              <Link key={user.id} to="/users/#{user.login}">{user.display_name}</Link>
-                <PromiseRenderer promise={conversation.get('messages', {page_size: 1, sort: '-created_at'})}>{(messages) =>
-
{timeAgo(messages[0].updated_at)}{', ' if i isnt (users.length-1)}

-                }</PromiseRenderer>
-
}
-

-      }</PromiseRenderer>
-
-      <Link to="/inbox/#{conversation.id}">
-        {if unread
-          }
-        {conversation.title}
-      </Link>
-

   render: ->

       {unless @props.user?

Please <button className="link-style" type="button" onClick={promptToSignIn}>sign in</button> to view your inbox

       else
-        <PromiseRenderer promise={@setConversations()} pending={-><Loading />}>{(conversations = []) =>
+

+
Inbox

+
+          {if @state.conversations.length is 0
+
You have not started any private conversations yet. Send users private messages by visiting their profile page.

+          else
+            conversationsMeta = @state.conversations[0].getMeta()
+

+              {@state.conversations.map (conversation) =>
+                <ConversationLink conversation={conversation} user={@props.user} key={conversation.id} />
+              }
+              <Paginator
+                page={+conversationsMeta.page}
+                pageCount={+conversationsMeta.page_count} />
+
}
+

-
Inbox

-
-            {if conversations.length is 0
-
You have not started any private conversations yet. Send users private messages by visiting their profile page.

-            else
-              conversationsMeta = conversations[0].getMeta()
-

-
{conversations.map(@conversationLink)}

-                <Paginator
-                  page={+conversationsMeta.page}
-                  pageCount={+conversationsMeta.page_count} />
-
}
-
-

-
Send a message

-              <InboxForm user={@props.user} />
-

+
Send a message

+            <InboxForm user={@props.user} />

-        }</PromiseRenderer>}
+

+        }


stuck path: hides it on focus to avoid mousedown glitch

--- components/search_bar/client/view.coffee
+++ components/search_bar/client/view.coffee
@@ -86,6 +86,7 @@
     _.delay =>
       @$el.removeClass('is-active')
     , 200
+    $('.path.stuck').show()

   clearSearch: ->
     @searchUnloaded()
@@ -93,3 +94,4 @@

   focusSearch: (e)->
     @$el.addClass('is-active')
+    $('.path.stuck').hide()


Fungi - Pull image from iDigBio servers.

--- app/controllers/interfaces/fungi.coffee
+++ app/controllers/interfaces/fungi.coffee
@@ -38,7 +38,7 @@
       @transcriber.spinner.hide()
       @transcriber.startTranscribing(Subject.current)

-    @transcriber.loadPhoto(Subject.current.location.standard, callback)
-    @transcriber.loadLargePhoto(Subject.current.location.standard)
+    @transcriber.loadPhoto(Subject.current.metadata.original_url, callback)
+    @transcriber.loadLargePhoto(Subject.current.metadata.original_url)

 module.exports = Fungi


Simplify up.eventListener

Simplify up.eventListener

--- lib/assets/javascripts/unpoly/classes/event_listener.coffee
+++ lib/assets/javascripts/unpoly/classes/event_listener.coffee
@@ -16,37 +16,35 @@
       # since we need to unbind the same @nativeCallback reference later.
       @callback.upEventListener = this

-    @buildNativeCallback()
     e.on(document, @eventNames, @nativeCallback)

   unbind: =>
     e.off(document, @eventNames, @nativeCallback)
     @callback.upEventListener = undefined

-  buildNativeCallback: ->
-    @nativeCallback = (event) =>
-      # 1. Since we're listing on `document`, event.currentTarget is now `document`.
-      # 2. event.target is the element that received an event, which might be a
-      #    child of `selector`.
-      # 3. There is only a single event bubbling up the DOM, so we are only called once.
+  nativeCallback: (event) =>
+    # 1. Since we're listing on `document`, event.currentTarget is now `document`.
+    # 2. event.target is the element that received an event, which might be a
+    #    child of `selector`.
+    # 3. There is only a single event bubbling up the DOM, so we are only called once.

-      element = event.target
-      element = e.closest(element, @selector) if @selector
+    element = event.target
+    element = e.closest(element, @selector) if @selector

-      if element
-        elementArg = if @jQuery then jQuery(element) else element
-        args = [event, elementArg]
+    if element
+      elementArg = if @jQuery then jQuery(element) else element
+      args = [event, elementArg]

-        # Do not retrieve and parse [up-data] unless the listener function
-        # expects a third argument. Note that we must pass data for an argument
-        # count of 0, since then the function might take varargs.
-        expectedArgCount = @callback.length
+      # Do not retrieve and parse [up-data] unless the listener function
+      # expects a third argument. Note that we must pass data for an argument
+      # count of 0, since then the function might take varargs.
+      expectedArgCount = @callback.length

-        unless expectedArgCount == 1 || expectedArgCount == 2
-          data = up.syntax.data(element)
-          args.push(data)
+      unless expectedArgCount == 1 || expectedArgCount == 2
+        data = up.syntax.data(element)
+        args.push(data)

-        @callback.apply(element, args)
+      @callback.apply(element, args)

   @fromBindArgs: (bindArgs, options = {}) ->
     bindArgs = u.copy(bindArgs)


修改密码加密算法

修改密码加密算法

--- core/model/account.coffee
+++ core/model/account.coffee
@@ -66,7 +66,7 @@
   return exports.sha256 crypto.randomBytes 256

 exports.hashPassword = (password, password_salt) ->
-  return exports.sha256(exports.sha256(password) + password_salt)
+  return exports.sha256(password_salt + exports.sha256(password))

 exports.register = (username, email, password, callback) ->
   password_salt = exports.randomSalt()


object.create during context.with, make new node.context during at() selection

--- src/context.coffee
+++ src/context.coffee
@@ -9,17 +9,15 @@
     # TODO: below is a bit of a hack...
     return @lookup('feature', name)?.binding

-  with: (options={}) ->
-    @opts ?= {}
-    @opts[k] = v for own k, v of options
-    return this
+  with: (options) ->
+    ctx = Object.create(this)
+    ctx.opts = Object.assign {}, @opts, options
+    Object.preventExtensions ctx
+    return ctx

   at: (key) ->
-    ctx = Object.create(this)
-    ctx.opts = Object.assign {}, @opts
-    ctx.node = @node.in key
-    Object.preventExtensions ctx
-    return ctx
+    node = @node.in key
+    return node?.context.with @opts

   push: (data) -> switch @kind
     when 'rpc', 'action' then @node.do(data, @opts)


Fixes #98

Fixes #98

--- lib/models/git-log.coffee
+++ lib/models/git-log.coffee
@@ -8,7 +8,7 @@
   atom.config.getPositiveInt('git-plus.amountOfCommitsToShow') ? (atom.config.getDefault 'git-plus.amountOfCommitsToShow')

 gitLog = (onlyCurrentFile=false) ->
-  args = ['log', '--pretty="%h;|%aN <%aE>;|%s;|%ar (%aD)"', '-s', "-n#{amountOfCommitsToShow()}"]
+  args = ['log', '--pretty=%h;|%aN <%aE>;|%s;|%ar (%aD)', '-s', "-n#{amountOfCommitsToShow()}"]
   args.push currentFile() if onlyCurrentFile and currentFile()?
   git.cmd
     args: args,


Scroll to top of media-title instead of media-header

--- src/scripts/modules/media/nav/nav.coffee
+++ src/scripts/modules/media/nav/nav.coffee
@@ -37,7 +37,7 @@
       @scrollToTop()

     scrollToTop: () ->
-      maxY = $('.media-header').offset().top
+      maxY = $('.media-title').offset().top
       y = window.pageYOffset or document.documentElement.scrollTop

       window.scrollTo(0, maxY) if y > maxY


fix: only print logger warning of error limit once (#136)

--- lib/product-import.coffee
+++ lib/product-import.coffee
@@ -233,7 +233,8 @@
   _errorLogger: (res, logger) =>
     if @_summary.failed < @errorLimit or @errorLimit is 0
       logger.error res, "Skipping product due to an error"
-    else
+    else if !@errorLimitHasBeenCommunicated
+      @errorLimitHasBeenCommunicated = true
       logger.warn "
         Error not logged as error limit of #{@errorLimit} has reached.
       "


feature(product-import): fix indentation

--- lib/product-import.coffee
+++ lib/product-import.coffee
@@ -426,7 +426,7 @@
         else
           @_prepareNewProduct(prodToProcess)
             .then (product) =>
-             Promise.resolve(@beforeCreateCallback(product))
+              Promise.resolve(@beforeCreateCallback(product))
                 .then () -> product
             .then (product) =>
               @client.products.create(product)


User event integration and unread()

User event integration and unread()

- Synced notifications to user event docs.
- Added unread().

--- src/Notifications.coffee
+++ src/Notifications.coffee
@@ -62,6 +62,12 @@
     collection.update id, $set: dateRead: new Date()
     if doc.eventId then UserEvents.read(eventId: doc.eventId)

+  unread: (id) ->
+    doc = collection.findOne(_id: id)
+    unless doc then throw new Error("Cannot mark unkown notification as unread: #{id}")
+    collection.update id, $unset: dateRead: null
+    if doc.eventId then UserEvents.unread(eventId: doc.eventId)
+
   getDocLabel: (doc) -> @_options.getDocLabel(doc)

   open: (doc) ->
@@ -105,6 +111,7 @@
 Collections.copy Events.getCollection(), collection,
   beforeInsert: (event) ->
     readAllDate = UserEventStats.get()?.readAllDate
+    # TODO(aramk) This isn't triggered since the event doc is published first.
     dateRead = UserEvents.getDateRead(eventId: event._id)
     if dateRead
       event.dateRead = dateRead
@@ -114,19 +121,19 @@
     delete event.doc
     event.eventId = event._id

+onUserEventChange = (doc) ->
+  dateRead = UserEvents.getDateRead eventId: doc.eventId
+  return unless dateRead?
+  notification = collection.findOne eventId: doc.eventId
+  Notifications.read(notification._id) if notification?
+
 # Reading individual events should mark their notification has read.
 Collections.observe UserEvents.getCollection(),
-  added: (doc) ->
-    dateRead = UserEvents.getDateRead eventId: doc.eventId
-    return unless dateRead?
+  added: onUserEventChange
+  changed: onUserEventChange
+  removed: (doc) ->
     notification = collection.findOne eventId: doc.eventId
-    Notifications.read(notification._id) if notification?
-
-# UserEvents.getCollection().after.update (userId, doc) ->
-#   dateRead = UserEvents.getDateRead eventId: doc.eventId
-#   return unless dateRead?
-#   notification = collection.findOne eventId: doc.eventId
-#   Notifications.read(notification._id) if notification?
+    Notifications.unread(notification._id) if notification?

 # Updating the read all date marks all event notifications as read.
 Tracker.autorun ->


Fix: 説明の画面が切り替わらないのを修正 refs #33

Fix: 説明の画面が切り替わらないのを修正 refs #33

--- src/gui/js/repo.coffee
+++ src/gui/js/repo.coffee
@@ -120,7 +120,11 @@
           return
         )
       else
-        webview.loadURL(@link)
+        detail.on("shown.bs.modal", ready = =>
+          detail.off("shown.bs.modal", ready)
+          webview.loadURL(@link)
+          return
+        )
       detail.modal("show")
       return
 )


change scroll sync logic

change scroll sync logic

--- lib/markdown-preview-enhanced-view.coffee
+++ lib/markdown-preview-enhanced-view.coffee
@@ -283,6 +283,9 @@
       # disable markdownHtmlView onscroll
       @previewScrollDelay = Date.now() + 500

+      # scroll preview to most top as editor is at most top.
+      return @scrollToPos(0) if firstVisibleScreenRow == 0
+
       # @element.scrollTop = @scrollMap[lineNo] - editorHeight / 2
       if lineNo of @scrollMap then @scrollToPos(@scrollMap[lineNo]-editorHeight / 2)

@@ -318,6 +321,10 @@
         return
       if Date.now() < @previewScrollDelay
         return
+
+      if @element.scrollTop == 0 # most top
+        @editorScrollDelay = Date.now() + 500
+        return @scrollToPos 0, @editor.getElement()

       top = @element.scrollTop + @element.offsetHeight / 2


kernel-manager: Fix issue #90

kernel-manager: Fix issue #90

* Do not parse languageMappings if null.

Fix #90

--- lib/kernel-manager.coffee
+++ lib/kernel-manager.coffee
@@ -64,14 +64,16 @@
             return language

     getLanguageMappings: ->
-        try
-            languageMappings = JSON.parse atom.config.get('Hydrogen.languageMappings')
-            return languageMappings
-        catch error
-            console.error error
-            languageMappings = {}
-            return languageMappings
-
+        languageMappings = atom.config.get 'Hydrogen.languageMappings'
+
+        if languageMappings
+            try
+                return JSON.parse languageMappings
+            catch error
+                console.error error
+
+        return {}
+
     getKernelInfoForLanguage: (language) ->
         kernels = @getAvailableKernels()
         console.log "Available kernels:", kernels


memoize getAvailableKernels

memoize getAvailableKernels

--- lib/kernel-manager.coffee
+++ lib/kernel-manager.coffee
@@ -7,9 +7,8 @@

 module.exports = KernelManager =
     runningKernels: {}
-    availableKernels: null

-    getAvailableKernels: ->
+    getAvailableKernels: _.memoize ->
         out = child_process.spawnSync('ipython',['kernelspec','list', '--json']).stdout.toString()
         _.pluck(JSON.parse(out).kernelspecs, 'spec')


Handle shifted numeric keys.

Handle shifted numeric keys.


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/active_bounds.coffee
+++ src/active_bounds.coffee
@@ -12,7 +12,7 @@

     bullet.include ActiveBounds

-    # => Now
+    # => Now the bullet will be re

 ActiveBounds module
 @name ActiveBounds


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/active_bounds.coffee
+++ src/active_bounds.coffee
@@ -4,7 +4,7 @@
     y: 0
     width: 8
     height: 8
-    collisionMargin: Point(0, 0)
+    activeBounds

   position: (newPosition) ->


Add function in icecast source to kill a connection with no data

If an icecast source stops sending data for 30 seconds, drop the connection.
Otherwise, a ghost connection could cause streaming to get stuck.

--- src/streammachine/sources/icecast.coffee
+++ src/streammachine/sources/icecast.coffee
@@ -7,6 +7,17 @@
         super()

         @emitDuration  = 0.5
+
+        # -- Alert if data stops flowing -- #
+
+        # creates a sort of dead mans switch that we use to kill the connection
+        # if it stops sending data
+        @_pingData = _u.debounce =>
+            # data has stopped flowing. kill the connection.
+            @log.info "Source data stopped flowing.  Killing connection."
+            @disconnect()
+
+        , 30*1000

         # data is going to start streaming in as data on req. We need to pipe
         # it into a parser to turn it into frames, headers, etc
@@ -25,6 +36,7 @@

         # outgoing -> Stream
         @parser.on "frame", (frame) =>
+            @_pingData()
             @emit "frame", frame

             # -- queue up frames until we get to @emitDuration -- #
@@ -106,7 +118,7 @@
             # source has gone away
             @emit "disconnect"
             @sock.end()
-
+
         # return with success
         @connected = true

@@ -122,6 +134,6 @@
     #----------

     disconnect: ->
-        @res.end()
+        @sock.end()
         @connected = false
         @emit "disconnect"


jupyter: fix backend test broken by adding shell message

--- src/smc-project/jupyter/test/basic.coffee
+++ src/smc-project/jupyter/test/basic.coffee
@@ -19,7 +19,7 @@
                 if err
                     done(err)
                 else
-                    expect(v).toEqual([{"metadata":{},"content":{"execution_state":"busy"},"buffers":[],msg_type: 'status'},{"metadata":{},"content":{"execution_count":1,"code":"2+2"},"buffers":[],msg_type: 'execute_input'},{"metadata":{},"content":{"execution_count":1,"data":{"text/plain":"4"},"metadata":{}},"buffers":[], msg_type: 'execute_result'},{"metadata":{},"content":{"execution_state":"idle"},"buffers":[], msg_type: 'status'}])
+                    expect(v.slice(0, v.length-1)).toEqual([{"metadata":{},"content":{"execution_state":"busy"},"buffers":[],msg_type: 'status'},{"metadata":{},"content":{"execution_count":1,"code":"2+2"},"buffers":[],msg_type: 'execute_input'},{"metadata":{},"content":{"execution_count":1,"data":{"text/plain":"4"},"metadata":{}},"buffers":[], msg_type: 'execute_result'},{"metadata":{},"content":{"execution_state":"idle"},"buffers":[], msg_type: 'status'}])
                     done()

     it 'closes the kernel', ->


A bit off in IE

A bit off in IE

Stolen from [1]

[1] https://miketaylr.com/posts/2014/11/document-body-scrolltop.html

--- resources/assets/coffee/osu_common.coffee
+++ resources/assets/coffee/osu_common.coffee
@@ -48,7 +48,8 @@

   bottomPage: ->
-    window.innerHeight == (document.body.scrollHeight - window.pageYOffset)
+    body = document.documentElement ? document.body.parent ? document.body
+    body.clientHeight == (body.scrollHeight - body.scrollTop)

   ajaxError: (xhr) ->


Fix incorrectly resolved merge conflict (75fadf1ba)

--- resources/assets/coffee/osu_common.coffee
+++ resources/assets/coffee/osu_common.coffee
@@ -254,7 +254,7 @@
     if user.is_bot
       'bot'
     else
-      _.intersection(['ppy', 'bot', 'dev', 'gmt', 'nat', 'bng', 'bng_limited', 'support', 'alumni'], _.concat(user.default_group, user.groups))[0]
+      _.intersection(['ppy', 'dev', 'gmt', 'nat', 'bng', 'bng_limited', 'support', 'alumni'], _.concat(user.default_group, user.groups))[0]

   navigate: (url, keepScroll, {action = 'advance'} = {}) ->


Added zoomOut feature called with double click and adjusted zoomIn scale.

--- app/scripts/controllers/district.coffee
+++ app/scripts/controllers/district.coffee
@@ -26,8 +26,8 @@
     $scope.setDistrict = (error, data) ->
       if not error
         unless data.length
-          $scope.warning = "No district was found for #{$scope.selected_zip}."
-        else $scope.state_district = {state: data[0].state, district: data[0].district}
+          return $scope.warning = "No district was found for #{$scope.selected_zip}."
+        $scope.state_district = {state: data[0].state, district: data[0].district}
       else
         console.log "Error: ", error

@@ -37,8 +37,7 @@
         $scope.district_element.attr('class', 'selected')
         $scope.bounding_box = $scope.district_element[0][0].getBoundingClientRect()
         $scope.district_element.call($scope.zoomIn)
-
-
+
     $scope.drawMap = () ->
       ready = (error, us, congress) ->
         $scope.usMap.append("defs").append("path").attr("id", "land").datum(topojson.feature(us, us.objects.land)).attr "d", path
@@ -57,6 +56,9 @@
         $scope.usMap.append("path").attr("class", "state-boundaries").datum(topojson.mesh(us, us.objects.states, (a, b) ->
           a isnt b
         )).attr "d", path
+        $('#map_holder').on("dblclick", () ->
+          $scope.zoomOut()
+        )
       width = 960
       height = 500
       path = d3.geo.path()
@@ -75,8 +77,15 @@
       bbox = element.getBBox()
       x = bbox.x + bbox.width/2
       y = bbox.y + bbox.height/2
-      scale = 10
+      scale = 200/Math.sqrt(Math.pow(bbox.width, 2) + Math.pow(bbox.height,2))
       $scope.usMap.transition().duration(750).attr("transform", "translate(" + 960 / 2 + "," + 600 / 2 + ")scale(" + scale + ")translate(" + -x + "," + -y + ")").style "stroke-width", 1.5 / scale + "px"
+
+    $scope.zoomOut = (d) ->
+      $scope.position = null
+      $scope.selected_zip = null
+      $scope.warning = null
+      $scope.state_district = {state: null, district: null}
+      $scope.usMap.transition().duration(750).attr("transform", "translate(0,0)scale(1)").style "stroke-width", 1 + "px"

     $scope.drawMap()


Remove command

Remove command

--- app/scripts/directives/recipient.coffee
+++ app/scripts/directives/recipient.coffee
@@ -107,19 +107,6 @@
           return false

         queryUserByEmail = (emails) ->
-          # for email in emails
-          #   conti = false
-          #   for user in allUser
-          #     if user.email == email
-          #       arr.push user
-          #       conti = true
-
-          #   if conti then continue;
-
-          #   arr.push {
-          #     name: email
-          #     email: email
-          #   }

           arr = emails.map (email)->


Added urn to delete log message

Added urn to delete log message

--- src/api/mediators.coffee
+++ src/api/mediators.coffee
@@ -124,8 +124,8 @@

   try {
     yield Mediator.findOneAndRemove({ urn: urn }).exec();
-    this.body = "Mediator successfully removed by "+this.authenticated.email;
-    logger.info('Mediator successfully removed by %s', this.authenticated.email);
+    this.body = "Mediator with urn '+urn+' has been successfully removed by "+this.authenticated.email;
+    logger.info('Mediator with urn '+urn+' has been successfully removed by %s', this.authenticated.email);
   }catch(e){
     logger.error('Could not remove Mediator by urn '+urn+' via the API: ' + e);
     this.body = e.message;


clear observer when done

clear observer when done

--- resources/assets/lib/back-to-top.coffee
+++ resources/assets/lib/back-to-top.coffee
@@ -31,7 +31,9 @@

   componentWillUnmount: =>
     document.removeEventListener 'scroll', @onScroll
-    @observer?.disconnect()
+    if @observer?
+      @observer.disconnect()
+      @observer = null

   onClick: (_e) =>
@@ -51,20 +53,20 @@
   onScroll: (_e) =>
     @setState lastScrollY: null
     document.removeEventListener 'scroll', @onScroll
+    if @observer?
+      @observer.disconnect()
+      @observer = null

   mountObserver: =>
     # workaround Firefox srollTo and setTimeout(fn, 0) not being dispatched serially
     if window.IntersectionObserver?
-      return if @observer?
-
       # anchor to body if none specified; assumes body's top is 0.
       target = @props.anchor?.current ? document.body

       callback = (entries) =>
         for entry in entries
           if entry.target == target && entry.boundingClientRect.top == 0
-            # also gets attached when scrolling downwards, but should be fine in this case.
             document.addEventListener 'scroll', @onScroll
             break


viewport can be smaller than element

viewport can be smaller than element

--- resources/assets/lib/back-to-top.coffee
+++ resources/assets/lib/back-to-top.coffee
@@ -65,7 +65,7 @@

       callback = (entries) =>
         for entry in entries
-          if entry.target == @props.anchor.current && entry.intersectionRatio == 1
+          if entry.target == @props.anchor.current && entry.boundingClientRect.top == 0
             # also gets attached when scrolling downwards, but should be fine in this case.
             document.addEventListener 'scroll', @onScroll
             break


case insensitive

case insensitive

--- src/archer-quotes.coffee
+++ src/archer-quotes.coffee
@@ -21,42 +21,54 @@
     "phrasing!"
   ]
   dangerZone: [
-    "call Kenny Loggins... 'cuz you're in the Danger Zone",
+    "call Kenny Loggins... 'cuz you're in the DANGER ZONE!",
     "Danger Zone!",
     "Paging Dr. Loggins",
     "The zone will be one of danger?"
     "https://www.youtube.com/watch?v=siwpn14IE7E"
   ]
   hadSomething: [
-    "hold on! I had something good for this"
+    "hold on! I had something good for this",
+    "Pam, wait! I had something… what was it? … Uh, something about stripes and solids.",
+    "Oh, good, you caught the, uh, oh wait, I had something good for this... the... 'Pita Predator'."
   ]

 module.exports = (robot) ->
   # TODO read this from an environment variable
-  robot.hear /comes|take it slow|swallow|long ride|happy ending|coming out|came|too hard|not hard/, (msg) ->
+  robot.hear /comes|take it slow|swallow|long ride|happy ending|coming out|came|hard/i, (msg) ->
     msg.send msg.random sayings.phrasing

-  robot.hear /danger|loggin|logins|obsess|life insurance/, (msg) ->
+  robot.hear /danger|loggin|logins|obsess|life insurance/i, (msg) ->
     msg.send msg.random sayings.dangerZone

-  robot.hear /situation|dealing|stress/, (msg) ->
+  robot.hear /situation|dealing|stress|!/i, (msg) ->
     msg.send msg.random sayings.hadSomething

-  robot.hear /ocelot/, (msg) ->
+  robot.hear /ocelot/i, (msg) ->
     msg.send "I've never seen an ocelot!"

-  robot.hear /karate/, (msg) ->
+  robot.hear /karate/i, (msg) ->
     msg.send "Karate? the Dane Cook of martial arts?"

-  robot.hear /call girl/, (msg) ->
+  robot.hear /call girl/i, (msg) ->
     msg.send "No Cyril, when they're dead, they're just hookers"

-  robot.hear /telephone|phone sex/, (msg) ->
+  robot.hear /telephone|phone sex/i, (msg) ->
     msg.send "does internet porn know you're cheating on it?"

-  robot.hear /ruse/, (msg) ->
+  robot.hear /ruse/i, (msg) ->
     msg.send "a ruse? brrring brrring ... hi, it's the 1930's, can we have our words and clothes and shitty airplane back?"

-  robot.hear /temperature|thermostat/, (msg) ->
+  robot.hear /temperature|thermostat/i, (msg) ->
     msg.send "the thermostats are becoming sentient!"

+  # robot.respond /archer me/i, (msg) ->
+  #   cheerio = require('cheerio')
+  #   # dom = robot.http('http://en.wikiquote.org/wiki/Archer_%28TV_series%29')
+  #   #            .get() (error, result, body) ->
+  #   #              if error
+  #   #                msg.send "Encountered an error :( #{error}"
+  #   #                return
+  #   #              $ = cheerio.load(body)
+  #   #              $('dl')
+


super :lipstick:

super :lipstick:

--- src/atom/vim-mode.coffee
+++ src/atom/vim-mode.coffee
@@ -1,6 +1,6 @@
 _ = require 'underscore'
 $ = require 'jquery'
-{ NumericPrefix, DeleteChar, MoveLeft, MoveUp} = require 'vim-mode-operators'
+op = require 'vim-mode-operators'

 module.exports =
 class VimMode
@@ -9,21 +9,33 @@

   constructor: (@editor) ->
     @opStack = []
-    atom.bindKeys '.command-mode', -> false
-    atom.bindKeys '.command-mode', @commandModeBindings()
-    atom.bindKeys '.insert-mode', '<esc>': 'command-mode:activate'
-
     @editor.addClass('command-mode')

-    @editor.on 'insert-mode:activate', => @activateInsertMode()
-    @editor.on 'command-mode:activate', => @activateCommandMode()
-    @editor.on 'command-mode:delete-char', => @pushOperator(new DeleteChar)
-    @editor.on 'command-mode:numeric-prefix', (e) => @pushOperator(new NumericPrefix(e.keyEvent.char))
-    @editor.on 'command-mode:move-left', => @pushOperator(new MoveLeft)
-    @editor.on 'command-mode:move-up', => @pushOperator(new MoveUp)
+    atom.bindKeys '.editor', '<esc>': 'activate-command-mode'
+    @editor.on 'activate-command-mode', => @activateCommandMode()

-  registerCommand: (name, handler) ->
-    @editor.on "command-mode:#{name}", handler
+    @setupCommandMode()
+
+  setupCommandMode: ->
+    atom.bindKeys '.command-mode', -> false
+
+    @registerCommand 'i', 'insert', => @activateInsertMode()
+    @registerCommand 'x', 'delete-char', => new op.DeleteChar
+    @registerCommand 'h', 'move-left', => new op.MoveLeft
+    @registerCommand 'j', 'move-up', => new op.MoveUp
+
+    for i in [0..9]
+      do (i) =>
+        @registerCommand i, "numeric-prefix-#{i}", => new op.NumericPrefix(i)
+
+  registerCommand: (binding, commandName, fn)->
+    bindings = {}
+    eventName = "command-mode:#{commandName}"
+    bindings[binding] = eventName
+    atom.bindKeys '.command-mode', bindings
+    @editor.on eventName, =>
+      possibleOperator = fn()
+      @pushOperator(possibleOperator) if possibleOperator.execute?

   activateInsertMode: ->
     @editor.removeClass('command-mode')
@@ -32,16 +44,6 @@
   activateCommandMode: ->
     @editor.removeClass('insert-mode')
     @editor.addClass('command-mode')
-
-  commandModeBindings: ->
-    bindings =
-      'i': 'insert-mode:activate'
-      'x': 'command-mode:delete-char'
-      'h': 'command-mode:move-left'
-      'j': 'command-mode:move-up'
-    for i in [0..9]
-      bindings[i] = 'command-mode:numeric-prefix'
-    bindings

   pushOperator: (op) ->
     @opStack.push(op)


Set loaded attr to true after fetching results

--- src/scripts/models/search-results.coffee
+++ src/scripts/models/search-results.coffee
@@ -16,4 +16,5 @@

     initialize: (options = {}) ->
       @query = options.query or ''
-      @fetch()
+      @fetch
+        success: () => @set('loaded', true)


form-control class

form-control class

--- public/views/cart_line_item_view.coffee
+++ public/views/cart_line_item_view.coffee
@@ -10,8 +10,10 @@

       <img src="<%= thumbnail %>">

+    <%= name %>
+    <%= singleItemPrice %>

-      <select class="epages-cart-overlay-line-item-quantity">
+      <select class="form-control class="epages-cart-overlay-line-item-quantity">
         <% _.map([1,2,3,4,5], function(num) { %>
           <option value="<%= num %>" <%= quantity === num ? "selected" : void 0 %> >
             <%= num %>
@@ -20,14 +22,12 @@
       </select>
       <%= unit %>

-    <%= name %>
-    <%= singleItemPrice %>
-    <%= lineItemPrice %>

       <button class="epages-cart-overlay-line-item-remove"
               alt="Remove product">
       </button>

+    <%= lineItemPrice %>
   """

   render: ->


Missing close the modal on the list view

--- public/views/product_detail_view.coffee
+++ public/views/product_detail_view.coffee
@@ -146,6 +146,7 @@
           if @model.attributes.productId == model.attributes.productId
             model.attributes.quantity += 1
       App.cart.sync()
+      App.modal.close()
     event.target.disabled = false

   updateVariations: =>


expandBufferRangeToLineEnds works for ranges that are folded on screen

--- src/atom/renderer.coffee
+++ src/atom/renderer.coffee
@@ -209,7 +209,8 @@

   expandBufferRangeToLineEnds: (bufferRange) ->
     { start, end } = bufferRange
-    new Range([start.row, 0], [end.row, @lineMap.lineForBufferRow(end.row).text.length])
+    endColumn = @lineMap.lineForBufferRow(end.row)?.text.length ? 0
+    new Range([start.row, 0], [end.row, endColumn])

   rangeForAllLines: ->
     new Range([0, 0], @clipScreenPosition([Infinity, Infinity]))


webapp: just a copyright note

webapp: just a copyright note

--- src/smc-webapp/editor_react_wrapper.cjsx
+++ src/smc-webapp/editor_react_wrapper.cjsx
@@ -1,11 +1,26 @@
-###
-Wrapper in a React component of a non-react editor, so that we can fully rewrite
-the UI using React without having to rewrite all the editors.
-
-GPL
-
-(c) 2016, SageMath, Inc.
-###
+##############################################################################
+#
+# SageMathCloud: A collaborative web-based interface to Sage, IPython, LaTeX and the Terminal.
+#
+#    Copyright (C) 2016, Sagemath Inc.
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation, either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+# Wrapper in a React component of a non-react editor, so that we can fully rewrite
+# the UI using React without having to rewrite all the editors.
+##############################################################################

 {NotifyResize} = require('react-notify-resize')


improve default cell delimiter

improve default cell delimiter

--- lib/package/config.coffee
+++ lib/package/config.coffee
@@ -223,7 +223,7 @@
       cellDelimiter:
         title: 'Cell Delimiter'
         type: 'array'
-        default: ['##\\s', '#---', '#\\s?%%']
+        default: ['##(?!#)', '#---', '#\\s?%%']
         description: 'Regular expressions for determining cell delimiters.'
         order: 11
       highlightCells:


same changes we made in axis.coffee - pull ranges from plot instead of parent - call get_obj to get range objects instead of get

--- static/coffee/renderers/guide/rule.coffee
+++ static/coffee/renderers/guide/rule.coffee
@@ -56,7 +56,7 @@
     i = @get('guidespec').dimension
     j = (i + 1) % 2

-    ranges = [@get_obj('parent').get('x_range'), @get_obj('parent').get('y_range')]
+    ranges = [@get_obj('plot').get_obj('x_range'), @get_obj('plot').get_obj('y_range')]

     user_bounds = @get('guidespec').bounds ? 'auto'
     range_bounds = [ranges[i].get('min'), ranges[i].get('max')]
@@ -80,8 +80,7 @@
   _rule_coords: () ->
     i = @get('guidespec').dimension
     j = (i + 1) % 2
-
-    ranges = [@get_obj('parent').get('x_range'), @get_obj('parent').get('y_range')]
+    ranges = [@get_obj('plot').get_obj('x_range'), @get_obj('plot').get_obj('y_range')]
     range = ranges[i]
     cross_range = ranges[j]


change to differentiate between explicit user values and defaults

--- static/coffee/renderers/properties.coffee
+++ static/coffee/renderers/properties.coffee
@@ -4,134 +4,137 @@
 class properties

   string: (styleprovider, glyphspec, attrname) ->
-    default_value = styleprovider.mget(attrname)
-
-    if not (attrname of glyphspec)
-      if _.isString(default_value)
-        @[attrname] = {default: default_value}
-      else
-        console.log("string property '#{ attrname }' given invalid default value: " + default_value)
+    @[attrname] = {}
+
+    default_value = styleprovider.mget(attrname)
+    if not default_value?
+    else if _.isString(default_value)
+      @[attrname].default = default_value
+    else
+      console.log("string property '#{ attrname }' given invalid default value: " + default_value)
+
+    if not (attrname of glyphspec)
       return

     glyph_value = glyphspec[attrname]
     if _.isString(glyph_value)
-      @[attrname] = {default: glyph_value}
-    else if _.isObject(glyph_value)
-      @[attrname] = glyph_value
-      if not @[attrname].default?
-        @[attrname].default = default_value
+      @[attrname].value = glyph_value
+    else if _.isObject(glyph_value)
+      @[attrname] = _.extend(@[attrname], glyph_value)
     else
       console.log("string property '#{ attrname }' given invalid glyph value: " + glyph_value)

   number: (styleprovider, glyphspec, attrname) ->
-    default_value = styleprovider.mget(attrname)
-    default_units = styleprovider.mget(attrname+'_units') ? 'data'
-
-    @[attrname + "_typed"] = true
-
+    @[attrname] = { typed: true }
+
+    default_value = styleprovider.mget(attrname)
+    if not default_value?
+    else if _.isNumber(default_value)
+      @[attrname].default = default_value
+    else
+      console.log("number property '#{ attrname }' given invalid default value: " + default_value)
+
+    units_value = styleprovider.mget(attrname+'_units') ? 'data'
     if (attrname+'_units' of glyphspec)
-      default_units = glyphspec[attrname+'_units']
-
-    if not (attrname of glyphspec)
-      if _.isNumber(default_value)
-        @[attrname] = {default: default_value, units: default_units}
-      else
-        console.log("number property '#{ attrname }' given invalid default value: " + default_value)
+      units_value = glyphspec[attrname+'_units']
+    @[attrname].units = units_value
+
+    if not (attrname of glyphspec)
       return

     glyph_value = glyphspec[attrname]
     if _.isString(glyph_value)
-      @[attrname] = {field: glyph_value, default: default_value, units: default_units}
+      @[attrname].field = glyph_value
     else if _.isNumber(glyph_value)
-      @[attrname] = {default: glyph_value, units: default_units}
-    else if _.isObject(glyph_value)
-      @[attrname] = glyph_value
-      if not @[attrname].default?
-        @[attrname].default = default_value
-      if not @[attrname].units?
-        @[attrname].units = default_units
+      @[attrname].value = glyph_value
+    else if _.isObject(glyph_value)
+      @[attrname] = _.extend(@[attrname], glyph_value)
     else
       console.log("number property '#{ attrname }' given invalid glyph value: " + glyph_value)

   color: (styleprovider, glyphspec, attrname) ->
-    default_value = styleprovider.mget(attrname)
-
-    if not (attrname of glyphspec)
-      if _.isNull(default_value)
-        @[attrname] = {default: null}
-      else if _.isString(default_value) and (svg_colors[default_value]? or default_value.substring(0, 1) == "#")
-        @[attrname] = {default: default_value}
-      else
-        console.log("color property '#{ attrname }' given invalid default value: " + default_value)
-      return
-
-    glyph_value = glyphspec[attrname]
-    console.log attrname, glyph_value
+    @[attrname] = {}
+
+    default_value = styleprovider.mget(attrname)
+    if not default_value?
+      @[attrname].default = null
+    else if _.isString(default_value) and (svg_colors[default_value]? or default_value.substring(0, 1) == "#")
+      @[attrname].default = default_value
+    else
+      console.log("color property '#{ attrname }' given invalid default value: " + default_value)
+
+    if not (attrname of glyphspec)
+      return
+
+    glyph_value = glyphspec[attrname]
     if _.isNull(glyph_value)
-      @[attrname] = {default: null}
+      @[attrname].value = null
     else if _.isString(glyph_value)
       if svg_colors[glyph_value]? or glyph_value.substring(0, 1) == "#"
-        @[attrname] = {default: glyph_value}
+        @[attrname].value = glyph_value
       else
-        @[attrname] = {field: glyph_value, default: default_value}
-    else if _.isObject(glyph_value)
-      @[attrname] = glyph_value
-      if not @[attrname].default?
-        @[attrname].default = default_value
+        @[attrname].field = glyph_value
+    else if _.isObject(glyph_value)
+      @[attrname] = _.extend(@[attrname], glyph_value)
     else
       console.log("color property '#{ attrname }' given invalid glyph value: " + glyph_value)
+    console.log attrname, @[attrname]

   array: (styleprovider, glyphspec, attrname) ->
-    default_value = styleprovider.mget(attrname)
-    default_units = styleprovider.mget(attrname+"_units") ? 'data'
-
+    @[attrname] = {}
+
+    default_value = styleprovider.mget(attrname)
+    if not default_value?
+    else if _.isArray(default_value)
+      @[attrname].default = default_value
+    else
+      console.log("array property '#{ attrname }' given invalid default value: " + default_value)
+
+    units_value = styleprovider.mget(attrname+"_units") ? 'data'
     if (attrname+'_units' of glyphspec)
-      default_units = glyphspec[attrname+'_units']
-
-    if not (attrname of glyphspec)
-      if _.isArray(default_value)
-        @[attrname] = {default: default_value, units: default_units}
+      units_value = glyphspec[attrname+'_units']
+    @[attrname].units = units_value
+
+    if not (attrname of glyphspec)
+      return
+
+    glyph_value = glyphspec[attrname]
+    if _.isString(glyph_value)
+      @[attrname].field = glyph_value
+    else if _.isArray(glyph_value)
+      @[attrname].value = glyph_value
+    else if _.isObject(glyph_value)
+      @[attrname] = _.extend(@[attrname], glyph_value)
+    else
+      console.log("array property '#{ attrname }' given invalid glyph value: " + glyph_value)
+
+  enum: (styleprovider, glyphspec, attrname, vals) ->
+    @[attrname] = {}
+
+    levels = vals.split(" ")
+
+    default_value = styleprovider.mget(attrname)
+    if _.isNull(default_value)
+    else if _.isString(default_value) and default_value in levels
+      @[attrname] = {default: default_value}
+    else
+      console.log("enum property '#{ attrname }' given invalid default value: " + default_value)
+      console.log("    acceptable values:" + levels)
+
+    if not (attrname of glyphspec)
+      return
+
+    glyph_value = glyphspec[attrname]
+    if _.isString(glyph_value)
+      if glyph_value in levels
+        @[attrname].value = glyph_value
       else
-        console.log("array property '#{ attrname }' given invalid default value: " + default_value)
-      return
-
-    glyph_value = glyphspec[attrname]
-    if _.isString(glyph_value)
-      @[attrname] = {field: glyph_value, default: default_value, units: default_units}
-    else if _.isArray(glyph_value)
-      @[attrname] = {default: glyph_value, units: default_units}
-    else if _.isObject(glyph_value)
-      @[attrname] = glyph_value
-      if not @[attrname].default?
-        @[attrname].default = default_value
-    else
-      console.log("array property '#{ attrname }' given invalid glyph value: " + glyph_value)
-
-  enum: (styleprovider, glyphspec, attrname, vals) ->
-    default_value = styleprovider.mget(attrname)
-    levels_value = vals.split(" ")
-
-    if not (attrname of glyphspec)
-      if _.isString(default_value) and default_value in levels_value
-        @[attrname] = {default: default_value}
-      else
-        console.log("enum property '#{ attrname }' given invalid default value: " + default_value)
-        console.log("    acceptable values:" + levels_value)
-      return
-
-    glyph_value = glyphspec[attrname]
-    if _.isString(glyph_value)
-      if glyph_value in levels_value
-        @[attrname] = {default: glyph_value}
-      else
-        @[attrname] = {field: glyph_value, default: default_value}
-    else if _.isObject(glyph_value)
-      @[attrname] = glyph_value
-      if not @[attrname].default?
-        @[attrname].default = default_value
+        @[attrname].field = glyph_value
+    else if _.isObject(glyph_value)
+      @[attrname] = _.extend(@[attrname], glyph_value)
     else
       console.log("enum property '#{ attrname }' given invalid glyph value: " + glyph_value)
-      console.log("    acceptable values:" + levels_value)
+      console.log("    acceptable values:" + levels)

   setattr: (styleprovider, glyphspec, attrname, attrtype) ->
     values = null
@@ -151,7 +154,7 @@

     # if the attribute is not on this property object at all, log a bad request
     if not (attrname of @)
-      #console.log("requested unknown property '#{ attrname }' on object: " + obj)
+      console.log("requested selection of unknown property '#{ attrname }' on object: " + obj)
       return

     # if the attribute specifies a field, and the field exists on the object, return that value
@@ -159,12 +162,16 @@
       if (@[attrname].field of obj)
         return obj[@[attrname].field]

+    # If the user gave an explicit value, that should always be returned
+    else if @[attrname].value?
+      return @[attrname].value
+
     # otherwise, if the attribute exists on the object, return that value
     if obj[attrname]?
       return obj[attrname]

     # finally, check for a default value on this property object that could be returned
-    if @[attrname]? and 'default' of @[attrname]
+    if @[attrname].default?
       return @[attrname].default

     # failing that, just log a problem
@@ -175,10 +182,10 @@

     # if the attribute is not on this property object at all, log a bad request
     if not (attrname of @)
-      #console.log("requested unknown property '#{ attrname }' on objects")
-      return
-
-    if @[attrname + "_typed"]?
+      console.log("requested vector selection of unknown property '#{ attrname }' on objects")
+      return
+
+    if @[attrname].typed?
       result = new Float32Array(objs.length)
     else
       result = new Array(objs.length)
@@ -190,6 +197,10 @@
       # if the attribute specifies a field, and the field exists on the object, return that value
       if @[attrname].field? and (@[attrname].field of obj)
         result[i] = obj[@[attrname].field]
+
+      # If the user gave an explicit value, that should always be returned
+      else if @[attrname].value?
+        result[i] = @[attrname].value

       # otherwise, if the attribute exists on the object, return that value
       else if obj[attrname]?
@@ -225,7 +236,7 @@
     @array(styleprovider, glyphspec, @line_dash_name)
     @number(styleprovider, glyphspec, @line_dash_offset_name)

-    @do_stroke = @[@line_color_name].default?
+    @do_stroke = not _.isNull(@[@line_color_name].value)

   set: (ctx, obj) ->
     ctx.strokeStyle = @select(@line_color_name, obj)
@@ -239,16 +250,16 @@

 class fill_properties extends properties
   constructor: (styleprovider, glyphspec, prefix="") ->
-    @fill_name       = "#{ prefix }fill_color"
+    @fill_color_name = "#{ prefix }fill_color"
     @fill_alpha_name = "#{ prefix }fill_alpha"

-    @color(styleprovider, glyphspec, @fill_name)
+    @color(styleprovider, glyphspec, @fill_color_name)
     @number(styleprovider, glyphspec, @fill_alpha_name)

-    @do_fill = @[@fill_name].default?
+    @do_fill = not _.isNull(@[@fill_color_name].value)

   set: (ctx, obj) ->
-    ctx.fillStyle   = @select(@fill_name,       obj)
+    ctx.fillStyle   = @select(@fill_color_name, obj)
     ctx.globalAlpha = @select(@fill_alpha_name, obj)


Remove ping/pong route

Remove ping/pong route

--- routes.coffee
+++ routes.coffee
@@ -26,6 +26,3 @@
   app.get '/logout', (req, res) ->
     req.logout()
     res.redirect '/'
-
-  app.get '/ping', (req, res) ->
-    res.send "pong!", 200


Fix beta check

Fix beta check

--- lib/plugin-manager.coffee
+++ lib/plugin-manager.coffee
@@ -132,7 +132,16 @@
         Promise.all(
           @plugins.map (plugin) =>
             @getLastestVersionOfPlugin(plugin).then (distTag) ->
-              version = if @config_.betaCheck && distTag.beta? then distTag.beta else distTag.latest
+              if !@config_.betaCheck
+                version = distTag.latest
+              else
+                if distTag.beta?
+                  if semver.gt distTag.beta, distTag.latest
+                    version = distTag.beta
+                  else
+                    version = distTag.latest
+                else
+                  version = distTag.latest
               plugin.lastestVersion = version
               plugin.isOutdated = semver.lt plugin.version, plugin.lastestVersion
         )


remove duplicated rank preview

remove duplicated rank preview

--- jsapp/xlform/src/view.row.coffee
+++ jsapp/xlform/src/view.row.coffee
@@ -455,7 +455,6 @@
         cid: model.cid
       template_args.rank_rows = rank_rows
       extra_score_contents = $viewTemplates.$$render('row.rankView', @, template_args)
-      @$('.card--selectquestion__expansion').eq(0).append(extra_score_contents).addClass('js-cancel-select-row')
       @$('.card--selectquestion__expansion').eq(0).append(extra_score_contents).addClass('js-cancel-select-row')
       @editRanks()
     editRanks: ->


Fixup ad8cd5c, regression for incomplete refactor.

--- src/helpers.coffee
+++ src/helpers.coffee
@@ -154,8 +154,8 @@
       @_debugGutter = 2 * @debugFontSize
       @_debugLine = kPhaserLineRatio * @debugFontSize

-      @debug = game.debug
-      @debug.font = "#{@debugFontSize}px Menlo"
+      @debugDisplay = game.debug
+      @debugDisplay.font = "#{@debugFontSize}px Menlo"

     resetDebugDisplayLayout: ->
       @_layoutX = @_debugGutter + kPhaserLayoutX
@@ -167,7 +167,7 @@
         @_layoutY += lines * @_debugLine

       else for own label, text of items
-        @debug.text text, @_layoutX, @_layoutY, null, @debug.font
+        @debugDisplay.text text, @_layoutX, @_layoutY, null, @debugDisplay.font
         @_layoutY += @_debugLine

   # Developing


Set up Notes editors.

Set up Notes editors.

--- src/bootstrap-elm.coffee
+++ src/bootstrap-elm.coffee
@@ -243,7 +243,16 @@

   sync.saveNoteWithSnapshot(newNote, html).done (note) ->
     app.ports.setCurrentNote.send note
-    console.debug "TODO load up the note editor with the current html"
+
+    setUpEditor (-> document.getElementById("current-note-title")), note.title, false, (mutations, node) ->
+      title = node.textContent
+
+      console.debug "update note title:", title
+
+    setUpEditor (-> document.getElementById("current-note-body")), html, true, (mutations, node) ->
+      body = node.innerHTML
+
+      console.debug "update note body:", body

 app.ports.execCommand.subscribe (command) ->
   document.execCommand command, false, null


add GroupModel#isMember

add GroupModel#isMember

--- src/node_modules/group-model/group-model.coffee
+++ src/node_modules/group-model/group-model.coffee
@@ -31,3 +31,6 @@
     isAdmin: (user) ->
       membership = @getMembership(user)
       membership and membership.isAdmin
+
+    isMember: (user) ->
+      not not @getMembership(user)


allow token list in disable

allow token list in disable

--- src/buffer-stream.coffee
+++ src/buffer-stream.coffee
@@ -59,6 +59,8 @@

     enable:  () => @enabled = on
     disable: (args...) =>
+        if args.length is 1 and isArray(args[0])
+            args = args[0]
         for splitter in args
             i = @splitters.indexOf(splitter)
             continue if i is -1


Update campaign editor view aspect ratio to match in-game (#36)

--- app/views/play/CampaignView.ozar.coffee
+++ app/views/play/CampaignView.ozar.coffee
@@ -1067,7 +1067,11 @@
   onWindowResize: (e) =>
     mapHeight = iPadHeight = 1536
     mapWidth = {dungeon: 2350, forest: 2500, auditions: 2500, desert: 2411, mountain: 2422, glacier: 2421}[@terrain] or 2350
-    aspectRatio = mapWidth / mapHeight
+
+    # Updated aspect ratio to mirror LayoutAspectRatioContainer
+    # TODO: switch ozaria compaign editor to use same view components as game views
+    aspectRatio = 1266 / 668
+
     pageWidth = @$el.width()
     pageHeight = @$el.height()
     pageHeight -= adContainerHeight if adContainerHeight = $('.ad-container').outerHeight()


Removing a redundant 'in'

Removing a redundant 'in'

--- app/assets/javascripts/darkswarm/filters/dates.js.coffee
+++ app/assets/javascripts/darkswarm/filters/dates.js.coffee
@@ -7,4 +7,4 @@
     if moment().add('days', 2) < moment(date)
       "Orders open"
     else
-      "Closing in #{date_in_wordsFilter(date)}"
+      "Closing #{date_in_wordsFilter(date)}"


Fixed merge problem

Fixed merge problem

--- app/assets/javascripts/models/page/search_page.js.coffee
+++ app/assets/javascripts/models/page/search_page.js.coffee
@@ -1,4 +1,4 @@
-arch in#= require models/data/query
+#= require models/data/query
 #= require models/data/datasets
 #= require models/data/dataset_facets
 #= require models/data/project


do not allow image when multipart

do not allow image when multipart

--- app/assets/javascripts/uploadcare/files/object.js.coffee
+++ app/assets/javascripts/uploadcare/files/object.js.coffee
@@ -60,6 +60,10 @@
             @__uploadDf.reject()

     multipartUpload: ->
+      if @settings.imagesOnly
+        @__rejectApi 'image'
+        return
+
       @multipartStart().done (data) =>
         @uploadParts(data.parts).done =>
           @multipartComplete(data.uuid).done (data) =>


adjust vulcanizer

adjust vulcanizer

--- lib/task/vulcanize.coffee
+++ lib/task/vulcanize.coffee
@@ -26,6 +26,7 @@
     options       = _.defaults (options || {}), @getDefaultOptions()
     fileMappings  = fileMappingList.resolve()
     args          = @prepareArguments(options)
+    console.log 'VULCANIZE', _.map fileMappings, (fileMapping) => fileMapping.src().path()

     @_ensureFolders(fileMappings, options)


collision with regular bounds

collision with regular bounds

--- test/collision.coffee
+++ test/collision.coffee
@@ -56,5 +56,5 @@

   start = Point(0, 0)

-  Collision.rayRectangle
+  Collision.rayRectangle start,
 module()


Add compile:coffee:watch task

Add compile:coffee:watch task

--- lib/tintan/compile.coffee
+++ lib/tintan/compile.coffee
@@ -23,12 +23,10 @@
     name: 'compile:coffee'   # name of the compiler task to generate

-  init: (tintan, options = {})->
-    @options = options || {}
+  init: (tintan, @options = {})->
     @options[k] = v for k,v of DEFAULT_OPTIONS when !@options.hasOwnProperty(k)
-    options = options
-
-
+    options = @options
+
     from = Tintan.$._(options.src)
     target = Tintan.$._(options.target)
     map = @map = compilerMap from, /\.coffee$/, (f)-> path.join(target, f).replace(/\.coffee$/, '.js')
@@ -52,6 +50,13 @@
       desc "Clean coffee-script produced files from #{options.target}"
       task 'clean', ->
         fs.unlink c for c in compiled
+
+    Tintan.$.onTaskNamespace options.name + ':watch', ->
+      desc "Watch coffee-script files in #{options.src} for changes and compile them into #{options.target}"
+      task 'watch', ->
+        c = spawn 'coffee', "--compile --watch --output #{options.target} #{options.src}".split(' ')
+        c.stdout.on 'data', (data)-> process.stdout.write data
+        c.stderr.on 'data', (data)-> process.stderr.write data
     true

    compile: (source, target, cb)->


fix(import): use new set sku action name

--- src/coffee/import.coffee
+++ src/coffee/import.coffee
@@ -265,7 +265,7 @@
         when 'setSearchKeywords' then header.has(CONS.HEADER_SEARCH_KEYWORDS) or header.hasLanguageForBaseAttribute(CONS.HEADER_SEARCH_KEYWORDS)
         when 'addToCategory', 'removeFromCategory' then header.has(CONS.HEADER_CATEGORIES)
         when 'setTaxCategory' then header.has(CONS.HEADER_TAX)
-        when 'setSKU' then header.has(CONS.HEADER_SKU)
+        when 'setSku' then header.has(CONS.HEADER_SKU)
         when 'addVariant', 'addPrice', 'removePrice', 'changePrice', 'addExternalImage', 'removeImage' then true
         when 'removeVariant' then @allowRemovalOfVariants
         else throw Error "The action '#{action.action}' is not supported. Please contact the commercetools support team!"


Added dispatching of proper javascript-events during replaying, based on the event-type originally recorded. Closes #12

--- src/coffee/replay.coffee
+++ src/coffee/replay.coffee
@@ -3,13 +3,20 @@
     @util = @main.util

   play: ->
+    ###
+  This function will replay all recorded events. This happens in several stages:
+   - recording is disabled
+   - all events in storage are looped through
+   - javascript Events are created matching the event from storage, and dispatched on the element from storage
+   - recording is re-enabled
+
+  TODO: Add graphical clicks to the elements being clicked, and ensure that the element in question is in the users current view.
+    ###
     @main.storage.record_input = false
     for stored_element in @main.storage.data
       @util.debug "replaying! Attempting to execute a #{stored_element.event_type} on #{stored_element.id}"
       element = document.getElementById stored_element.id
-
-      # this, naturally needs refinement. Assuming click is baaad..
-      # Should probably create a proper event, rather than just running click.
-      element.click()
+      event = new Event(stored_element.event_type)
+      element.dispatchEvent(event)

     @main.storage.record_input = true


Stop tree-view from blocking tab-switching

--- .config/keymap.cson
+++ .config/keymap.cson
@@ -58,6 +58,16 @@
 	"ctrl-shift-R":   "window:run-package-specs"
 	"ctrl-alt-cmd-l": "unset!"

+".platform-darwin .tree-view":
+	"cmd-1":          "unset!"
+	"cmd-2":          "unset!"
+	"cmd-3":          "unset!"
+	"cmd-4":          "unset!"
+	"cmd-5":          "unset!"
+	"cmd-6":          "unset!"
+	"cmd-7":          "unset!"
+	"cmd-8":          "unset!"
+	"cmd-9":          "unset!"

 "atom-text-editor":
 	"cmd-l":          "grammar-selector:show"