Removed Array.toString and added Stack

Removed Array.toString and added Stack

--- src/lib.coffee
+++ src/lib.coffee
@@ -22,26 +22,6 @@

     @writeStream: (string) ->
       new StringBuffer(string)
-
-  # Prints anything onto a StringBuffer.
-  printOnto = (x, ws) ->
-    if not x?
-      ws.nextPutAll "" + x
-    else if x.constructor == Array
-      ws.nextPutAll "["
-      for elem, i in x
-        if (i > 0)
-          ws.nextPutAll ", "
-        printOnto elem, ws
-      ws.nextPutAll "]"
-    else
-      ws.nextPutAll x.toString()
-
-  # Makes Arrays print themselves sensibly.
-  Array.prototype.toString = ->
-    ws = StringBuffer.writeStream ""
-    printOnto this, ws
-    ws.contents()

   # delegation - switched for subclassing
   objectThatDelegatesTo = (x, props) ->
@@ -72,6 +52,21 @@

     values: ->
       key for own key of @data
+
+  class Stack
+    constructor: ->
+      @data = []
+
+    push: (v) ->
+      @data.push v
+      this
+
+    pop: ->
+      @data = @data[0..-2]
+      this
+
+    top: ->
+      @data[-1..][0]

   # some functional programming stuff - never used

@@ -175,3 +170,5 @@
   programString:    toProgramString
   subclass:         objectThatDelegatesTo
   StringBuffer:     StringBuffer
+  Set:              Set
+  Stack:            Stack


disable maxlisteners warning because its quit easy to have more than 10 tags

--- src/xml.coffee
+++ src/xml.coffee
@@ -105,6 +105,7 @@
         @pretty = opts.pretty ? off
         @level = opts.level ? 0
         @builder = opts.builder # inheritence
+        @setMaxListeners(0)
         @parent = @builder
         @closed = no
         @writable = true
@@ -278,6 +279,7 @@
         # defaults
         @opts.pretty ?= off
         @level = @opts.level ? -1
+        @setMaxListeners(0)
         # api
         @Tag = Tag
         @tag = new_tag


* _elements и merge вынесены из класса  * Добавлен метод length  * Исправлены next и prev  * Изменены ключи свойств  * Убраны лишние return

--- storage.coffee
+++ storage.coffee
@@ -1,61 +1,59 @@
+"use strict"
+
+merge = (o1, o2) ->
+    return o1 if o1 == null or o2 == null
+    for key of o2
+        o1[key] = o2[key] if o2.hasOwnProperty key
+    o1
+
+_elements = {}
+counter = 0

 class ChocolateStorage

-    merge = (o1, o2) ->
-        return o1 if o1 == null or o2 == null
-        for key of o2
-            o1[key] = o2[key] if o2.hasOwnProperty key
-        o1
-
-    constructor: () ->
-        @_elements = []
-        @length = 0
-
     add: (options) ->
-        @_elements[@length] = merge {
-                'i': @length, # ID
-                't': '',      # title
-                'p': '',      # preview
-                'f': '',      # filename
-                'o': '',      # original
-                'w': null,    # width
-                'h': null     # height
+        _elements[counter] = merge {
+                'cid': counter, # ID
+                'name': '',    # title
+                'thumb': '',   # preview
+                'file': '',    # filename
+                'orig': '',    # original
+                'w': null,     # width
+                'h': null      # height
             }, options
-        merge {}, @_elements[@length++]
+        merge {}, _elements[counter++]

     get: (key, fn) ->
-        item = @_elements[key]
-        if item.h? and item.w? or item.o == ''
+        item = _elements[key]
+        if item.h isnt null and item.w isnt null or item.o is ''
             fn item
-            return item
-        image = new Image
-        image.src = item.o
+        image = new Image()
+        image.src = item.orig
         image.addEventListener "error", () ->
-            item.o = ''
+            item.orig = ''
             item.w = 0
             item.h = 0
             fn item
-            item
         , false
         image.addEventListener "load", () ->
             item.w = image.naturalWidth
             item.h = image.naturalHeight
             fn item
-            item
         , false
-        item

     next: (key, fn) ->
-        if ++key < @length
-            console.log 'next '+key
-            return @get key, fn
-        false
+        len = @length()
+        while not _elements[++key]? and key < len then
+        @get key, fn if _elements[key]?

     prev: (key, fn) ->
-        if --key > -1
-            console.log 'prev '+key
-            return @get key, fn
-        false
+        while not _elements[--key]? and key > -1 then
+        @get key, fn if _elements[key]?

+    length: () ->
+        i = 0
+        for key of _elements
+            i++ if _elements.hasOwnProperty key
+        i

 window.ChocolateStorage = ChocolateStorage


Support fenced yaml code blocks

Support fenced yaml code blocks

Refs atom/language-gfm#17

--- lib/extension-helper.coffee
+++ lib/extension-helper.coffee
@@ -20,6 +20,8 @@
   'sh': 'sh'
   'toml': 'toml'
   'xml': 'xml'
+  'yaml': 'yaml'
+  'yml': 'yaml'

 module.exports =
   extensionForFenceName: (fenceName) ->


Use interactive mode for 'find'

Use interactive mode for 'find'

--- lib/ghc-modi-process.coffee
+++ lib/ghc-modi-process.coffee
@@ -168,6 +168,8 @@
     rootDir = @getRootDir(buffer)

     @queueCmd 'find',
+      interactive: true
+      dir: rootDir
       options: Util.getProcessOptions(rootDir.getPath())
       command: 'find'
       args: [symbol]


Cleaner debug vers,caps

Cleaner debug vers,caps

--- lib/ghc-modi-process.coffee
+++ lib/ghc-modi-process.coffee
@@ -71,8 +71,7 @@
     if atLeast [5, 5]
       caps.rootExec = false
       caps.quoteArgs = true
-    Util.debug vers
-    Util.debug caps
+    Util.debug vers, caps
     @backend = new GhcModiProcessReal caps

   createQueues: =>


:fire: Remove an unneeded sort

:fire: Remove an unneeded sort

--- lib/message-registry.coffee
+++ lib/message-registry.coffee
@@ -31,10 +31,6 @@
       @linterResponses.forEach (messages) -> publicMessages = publicMessages.concat(messages)
       @editorMessages.forEach (linters) -> linters.forEach (messages) ->
         publicMessages = publicMessages.concat(messages)
-      @publicMessages = publicMessages.sort (a, b) ->
-        return -1 if a < b
-        return 1 if a > b
-        return 0
       @emitter.emit 'did-update-messages', @publicMessages
     requestAnimationFrame => @updatePublic()


Convert line endings on save

Convert line endings on save

--- lib/models/synced-fs.coffee
+++ lib/models/synced-fs.coffee
@@ -36,11 +36,12 @@

       editor.onDidSave =>
         console.log 'Saving: Path - ' + this.formatFilePath(buffer.file.path) + ' Matches? - ' + !!this.formatFilePath(buffer.file.path).match(/\.atom\/code/)
-        if this.formatFilePath(buffer.file.path).match(/\.atom\/code/)
+        if @formatFilePath(buffer.file.path).match(/\.atom\/code/)
           if @connectionState == 'closed'
             @popupNoConnectionWarning()

           @sendSave(editor.project, buffer.file, buffer)
+
       editor.onDidChangePath =>
         console.log('PATH CHANGED')

@@ -103,19 +104,20 @@
       panel.destroy()

   sendSave: (project, file, buffer) ->
-    ipc.send 'fs-local-save', JSON.stringify({
-      action: 'local_save',
-      project: {
-        path: this.formatFilePath(project.getPaths()[0])
-      },
-      file: {
-        path: this.formatFilePath(file.path)
-        digest: file.digest,
-      },
-      buffer: {
-        content: window.btoa(unescape(encodeURIComponent(buffer.getText())))
-      }
-    })
+    @convertLineEndingsToUnixFormat(buffer).then =>
+      ipc.send 'fs-local-save', JSON.stringify({
+        action: 'local_save',
+        project: {
+          path: this.formatFilePath(project.getPaths()[0])
+        },
+        file: {
+          path: this.formatFilePath(file.path)
+          digest: file.digest,
+        },
+        buffer: {
+          content: window.btoa(unescape(encodeURIComponent(buffer.getText())))
+        }
+      })

   #formattedText: (text) ->
     #try
@@ -128,3 +130,20 @@
       return path.replace(/(.*:\\)/, '/').replace(/\\/g, '/')
     else
       return path
+
+  convertLineEndingsToUnixFormat: (buffer) ->
+    return new Promise (resolve, reject) ->
+      format = '\n'
+      lastRowIndex = buffer.getLastRow()
+      buffer.transact ->
+        for rowIndex in [0...lastRowIndex]
+          do (rowIndex) ->
+            currEol = buffer.lineEndingForRow rowIndex
+            if currEol isnt format
+              lineEndingRange = new Range(
+                new Point(rowIndex, buffer.lineLengthForRow(rowIndex)),
+                new Point(rowIndex + 1, 0)
+              )
+              buffer.setTextInRange lineEndingRange, format,
+                { normalizeLineEndings: false }
+      resolve true


DOM: Moved the assert in 'replaceWith' method to the beginning. See #51152

--- src/base/dom.coffee
+++ src/base/dom.coffee
@@ -928,6 +928,7 @@
 	# new nodes can be node or Array of nodes
 	@replaceWith: (node, new_node) ->
 		CUI.util.assert(node instanceof Node and (new_node instanceof Node or new_node instanceof NodeList), "CUI.dom.replaceWidth", "nodes need to be instanceof Node.", node: node, newNode: new_node)
+		CUI.util.assert(node.parentNode instanceof Node, "CUI.dom.replaceWith", "parentNode of node needs to be an instance of Node", node: node, parentNode: node.parentNode)

 		if new_node instanceof NodeList
 			first_node = new_node[0]
@@ -936,7 +937,6 @@
 				@insertAfter(first_node, new_node[new_node.length-1])
 			return first_node
 		else
-			CUI.util.assert(node.parentNode instanceof Node, "CUI.dom.replaceWith", "parentNode of node needs to be an instance of Node", node: node, parentNode: node.parentNode)
 			return node.parentNode.replaceChild(new_node, node)

 	@getRect: (docElem) ->


add methods to rotten api provider

add methods to rotten api provider

EVEN IF THEY DON’T WANT YOU TO USE THEIR API WITHOUT PAYING $60,000 a
year

--- app/scripts/providers/rotten.coffee
+++ app/scripts/providers/rotten.coffee
@@ -90,6 +90,10 @@
                 small: raw.posters.thumbnail
                 medium: raw.posters.detailed
                 large: raw.posters.original
+            background:
+                small: ""
+                medium: ""
+                large: ""
             raw: raw

@@ -110,6 +114,35 @@
                 small: raw.posters.thumbnail
                 medium: raw.posters.detailed
                 large: raw.posters.original
+            background:
+                small: ""
+                medium: ""
+                large: ""
+
+
+        ###*
+         # Generate an empty object when movie is not recognized
+         # @method toEmpty
+         # @param {String} filename - Sanitized filename (the.best.movie.ever.720p.HDTV.mkv becomes "The best movie ever")
+         # @param {String} path - original filepath
+         # @return {Object}
+        ###
+        @toEmpty = (filename, path) ->
+            id: ""
+            rating: 0
+            title: filename
+            path: path
+            synopsis: ""
+            year: (new Date()).getFullYear()
+            cover:
+                small: ""
+                medium: ""
+                large: ""
+            background:
+                small: ""
+                medium: ""
+                large: ""
+            raw: {}

         @


remove trailing whitespace

remove trailing whitespace

--- app/services/TrackingService.coffee
+++ app/services/TrackingService.coffee
@@ -18,5 +18,4 @@
     else
       ga 'send', 'pageview'

-
 module.exports = TrackingService


Use _this instead of self

Use _this instead of self

--- app/assets/javascripts/gl_crop.js.coffee
+++ app/assets/javascripts/gl_crop.js.coffee
@@ -44,9 +44,9 @@
     $(selector, @form)

   bindEvents: ->
-    self = @
+    _this = @
     @fileInput.on 'change', (e) ->
-      self.onFileInputChange(e, @)
+      _this.onFileInputChange(e, @)

     @pickImageEl.on 'click', @onPickImageClick
     @modalCrop.on 'shown.bs.modal', @onModalShow
@@ -54,14 +54,14 @@
     @uploadImageBtn.on 'click', @onUploadImageBtnClick
     @cropActionsBtn.on 'click', (e) ->
       btn = @
-      self.onActionBtnClick(btn)
+      _this.onActionBtnClick(btn)
     @croppedImageBlob = null

   onPickImageClick: =>
     @fileInput.trigger('click')

   onModalShow: =>
-    self = @
+    _this = @
     @modalCropImg.cropper(
       viewMode: 1
       center: false
@@ -78,11 +78,12 @@
       cropBoxResizable: false
       toggleDragModeOnDblclick: false
       built: ->
-        container = $(@).cropper 'getContainerData'
-        cropBoxWidth = self.cropBoxWidth;
-        cropBoxHeight = self.cropBoxHeight;
+        $image = $(@)
+        container = $image.cropper 'getContainerData'
+        cropBoxWidth = _this.cropBoxWidth;
+        cropBoxHeight = _this.cropBoxHeight;

-        $(@).cropper('setCropBoxData',
+        $image.cropper('setCropBoxData',
           width: cropBoxWidth,
           height: cropBoxHeight,
           left: (container.width - cropBoxWidth) / 2,
@@ -113,11 +114,11 @@
     @readFile(input)

   readFile: (input) ->
-    self = @
+    _this = @
     reader = new FileReader
     reader.onload = ->
-      self.modalCropImg.attr('src', reader.result)
-      self.modalCrop.modal('show')
+      _this.modalCropImg.attr('src', reader.result)
+      _this.modalCrop.modal('show')

     reader.readAsDataURL(input.files[0])


remove legacy js

remove legacy js

--- app/assets/javascripts/records.js.coffee
+++ app/assets/javascripts/records.js.coffee
@@ -1,3 +0,0 @@
-# Place all the behaviors and hooks related to the matching controller here.
-# All this logic will automatically be available in application.js.
-# You can use CoffeeScript in this file: http://coffeescript.org/


clear plans list before adding them. fixes #55

clear text from itineraries list in plan view before adding itineraries.
this should prevent multiple itineraries from appearing because only the
last call will persist.

--- app/assets/transit/lib/views/plan.coffee
+++ app/assets/transit/lib/views/plan.coffee
@@ -66,6 +66,8 @@
     window.plan = plan
     console.log plan
     @$('.subnav h3').text("#{plan.get('from').name} to #{plan.get('to').name}")
+    @$('.progress').hide()
+    @$('.itineraries').html('')
     plan.get('itineraries').each (trip, index) =>
       trip.set('index', index + 1)
       console.log trip
@@ -77,7 +79,6 @@
         view.undelegateEvents()
         view.render_map()
       @$('.itineraries').append(view.render().el)
-      @$('.progress').hide()

   fit_bounds: =>


Possible fix for no data issue

Possible fix for no data issue

--- pimatic-smartmeter.coffee
+++ pimatic-smartmeter.coffee
@@ -50,20 +50,20 @@
       P1DataStream = require "./p1meterdata"
       p1datastream = new P1DataStream({portName: @portName})
       p1datastream.on 'data', (data) =>
-        if @actualusage != data.currentUsage
-          @actualusage = data.currentUsage
+        if @actualusage != Number data.currentUsage
+          @actualusage = Number data.currentUsage
           @emit "Actual usage", Number @actualusage

-        if @activetariff != data.currentTariff
-          @activetariff = data.currentTariff
+        if @activetariff != Number data.currentTariff
+          @activetariff = Number data.currentTariff
           @emit "Active tariff", Number @activetariff

-        if @tariff1totalusage != data.tariffOneTotalUsage
-          @tariff1totalusage = data.tariffOneTotalUsage
+        if @tariff1totalusage != Number data.tariffOneTotalUsage
+          @tariff1totalusage = Number data.tariffOneTotalUsage
           @emit "Total usage T1", Number @tariff1totalusage

-        if @tariff2totalusage != data.tariffTwoTotalUsage
-          @tariff2totalusage = data.tariffTwoTotalUsage
+        if @tariff2totalusage != Number data.tariffTwoTotalUsage
+          @tariff2totalusage = Number data.tariffTwoTotalUsage
           @emit "Total usage T2", Number @tariff1totalusage


Update oribi.coffee

Update oribi.coffee

--- app/lib/field-guide-content/oribi.coffee
+++ app/lib/field-guide-content/oribi.coffee
@@ -3,7 +3,7 @@
     Similar in appearance to a small gazelle, the oribi has a slender build, long neck and limbs, and a short tail. The coat is short, sleek, and tan to bright reddish or yellowish with contrasting white underparts, rump, and throat. The inside of the ears are also white, and the oribi has a white line over its eyes.
   '''

-  scientificName: '(Ourebia ourebi)'
+  scientificName: 'Ourebia ourebi'
   mainImage: 'assets/fieldguide-content/mammals/oribi/oribi-feature.jpg'

   conservationStatus: 'Least Concern' # Options are Least Concern, Near Threatened, Vulnerable, and Endangered; all have their own little icons as well.


Allow to show actions with negative layer priority (#6572)

Co-authored-by: Valentin Briukhanov <1c97702c869e5316729111bfe70e91c2a0718c7e@Valentins-MacBook-Air.local>

--- app/lib/surface/LayerAdapter.coco.coffee
+++ app/lib/surface/LayerAdapter.coco.coffee
@@ -217,8 +217,9 @@
     if lank.thangType.get('raster')
       @upsertActionToRender(lank.thangType)
     else
+      defaultRenderableActions = ThangType.defaultActions.concat(lank.thangType.get('preLoadActions') or [])
       for action in _.values(lank.thangType.getActions())
-        continue unless _.any ThangType.defaultActions, (prefix) -> _.string.startsWith(action.name, prefix)
+        continue unless _.any defaultRenderableActions, (prefix) -> _.string.startsWith(action.name, prefix)
         @upsertActionToRender(lank.thangType, action.name, lank.options.colorConfig)

   upsertActionToRender: (thangType, actionName, colorConfig) ->


add scoping for jquery

add scoping for jquery

--- apps/articles/client/article.coffee
+++ apps/articles/client/article.coffee
@@ -115,8 +115,8 @@

   renderPromotedTemplate: (name, href) ->
     console.log 'promoted template'
-    $('.article-section-header').hide()
-    $('#article-body-container').addClass('promoted').prepend promotedTemplate
+    @$('.article-section-header').hide()
+    @$('#article-body-container').addClass('promoted').prepend promotedTemplate
       name: name
       href: href


KodingProvider: add more checks for provided numbers, do not except if NaN

--- workers/social/lib/social/models/computeproviders/koding.coffee
+++ workers/social/lib/social/models/computeproviders/koding.coffee
@@ -24,6 +24,11 @@
     { r: { group, user, account }, clientIP } = client

     storage ?= 3
+
+    if isNaN storage
+      return callback new KodingError \
+      'Requested storage size is not valid.', 'WrongParameter'
+
     userIp   = clientIP or user.registeredFrom?.ip
     provider = 'koding'

@@ -100,7 +105,10 @@
                always on vm limit has been reached.""", "UsageLimitReached"

         if resize?
-          if resize > userPlan.storage
+          if isNaN resize
+            return callback new KodingError \
+            'Requested new size is not valid.', 'WrongParameter'
+          else if resize > userPlan.storage
             return callback new KodingError \
             """Requested new size exceeds allowed
                limit of #{userPlan.storage}GB.""", "UsageLimitReached"
@@ -115,7 +123,7 @@
             { _id : ObjectId machineId }
             { uid : machineId }
           ]
-          users   : $elemMatch: id: user.getId()
+          users   : $elemMatch: id: user.getId(), sudo: yes, owner: yes
           groups  : $elemMatch: id: group.getId()

         JMachine.one selector, (err, machine)->
@@ -128,6 +136,7 @@

           if alwaysOn?
             fieldsToUpdate['meta.alwaysOn'] = alwaysOn
+
           if resize?

             storageSize = machine.meta?.storage_size ? 3


Scrolling page up before showing overlay.

--- app/assets/javascripts/reports.js.coffee
+++ app/assets/javascripts/reports.js.coffee
@@ -354,12 +354,15 @@
                 console.log("ping")
                 if window.pings_missed > 10
                         $(".ping-fail-overlay").css("display":"none")
+                        $("body").css("overflow":"visible")
                 window.pings_missed = 0
                 )
         window.ping_check_interval = window.setInterval( (() =>
                 window.pings_missed += 1
                 if window.pings_missed > 10
                         $(".ping-fail-overlay").css("display":"block")
+                        $("body").css("overflow":"hidden")
+                        window.scrollTo(0,0)
                 ), 1000)


improve spacing accuracy

improve spacing accuracy

--- app/assets/javascripts/zvg/column.coffee
+++ app/assets/javascripts/zvg/column.coffee
@@ -168,6 +168,7 @@
       .attr('transform', (d,i) => "translate(#{@column_band(i)}, 0)")
       # FIXME: this dims columns below a certain value threshold, but does
       # not work with the overridden series_2_label_sum function below.
+
       #.attr('opacity', (d) =>
       #if @series_2_label_sum(d) < (@_n_threshold or 0) then 0.3 else 1.0
       #)
@@ -335,13 +336,12 @@
     series_set.add(@survey_title_to_series_name[d.key])for d,i in @_data
     series_count = series_set.values().length

-    @column_spacing      = (@width - @x_offset)/(total_column_count + @_data.length + (series_count))
+    @column_spacing      = (@width - @x_offset)/(total_column_count + @_data.length + (series_count - 1))
     @column_padding      = 0.1 * @column_spacing
-    @series_padding      = 1.5 * @column_spacing / 2
+    @series_padding      = @column_spacing / 2
     current_x            = @x_offset # allow for scale on left
     maxCount             = d3.max(@_data, (d) -> d.values.length)
     current_series       = undefined
-

     for d,i in @_data
       do (d,i) =>
@@ -358,5 +358,6 @@
     @column_band = d3.scale.ordinal()
       .domain([0...maxCount])
       .rangeRoundBands([0, @column_spacing * maxCount], 0.1)
-    series_padding_sum = d3.max([0, series_count]) * @series_padding
-    @widen_chart((@width - @x_offset) + 100 + series_padding_sum) if @column_band.rangeBand() < @minimum_column_width
+    series_padding_sum = d3.max([0, series_count - 1]) * @series_padding
+    if @column_band.rangeBand() < @minimum_column_width
+      @widen_chart(@width - @x_offset + 100)


SocialWorker: refactor addToGroupByInvitation

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -520,9 +520,11 @@
           queue.next()

       =>
-        @addToGroupByInvitation {
-          groupName, groupIsBeingCreated, account, user, invitation
-        }, queue, callback
+        return queue.next()  if groupIsBeingCreated
+
+        @addToGroupByInvitation { groupName, account, user, invitation }, (err) ->
+          return callback err  if err
+          queue.next()

       ->
         return queue.next()  if groupIsBeingCreated
@@ -619,11 +621,10 @@
       user.verifyByPin options, callback

-  @addToGroupByInvitation = (options, queue, callback) ->
-
-    { groupName, groupIsBeingCreated, account, user, invitation } = options
-
-    return queue.next()  if groupIsBeingCreated
+  @addToGroupByInvitation = (options, callback) ->
+
+    { groupName, account, user, invitation } = options
+
     # check for membership
     JGroup.one { slug: groupName }, (err, group) ->

@@ -631,13 +632,13 @@
       return callback new KodingError 'group doesnt exist'  if not group

       group.isMember account, (err, isMember) ->
-        return callback err  if err
-        return queue.next()  if isMember # if user is already member, we can continue
+        return callback err   if err
+        return callback null  if isMember # if user is already member, we can continue

         # addGroup will check all prerequistes about joining to a group
         JUser.addToGroup account, groupName, user.email, invitation, (err) ->
           return callback err  if err
-          return queue.next()
+          return callback null

   redeemInvitation = (options, callback) ->


juser: pass recovery token to client

juser: pass recovery token to client

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -697,6 +697,7 @@
     invite         = null
     user           = null
     quotaExceedErr = null
+    recoveryToken  = null

     queue = [
       =>
@@ -769,7 +770,8 @@
           resetPassword : no
           expiryPeriod  : 1000 * 60 * 60 * 24 * 14 # 2 weeks in milliseconds

-        JPasswordRecovery.create client, passwordOptions, (err)->
+        JPasswordRecovery.create client, passwordOptions, (err, token)->
+          recoveryToken = token
           queue.next()
       ->
         JPaymentSubscription.createFreeSubscription account, (err) ->
@@ -792,7 +794,7 @@
         mixpanel.track "Signup from server, success"
         queue.next()
       ->
-        callback quotaExceedErr, newToken
+        callback quotaExceedErr, newToken, recoveryToken
         queue.next()
     ]


remove Ext 4's hasListener hacks

remove Ext 4's hasListener hacks

--- packages/deft/test/coffee/mvc/ViewController.coffee
+++ packages/deft/test/coffee/mvc/ViewController.coffee
@@ -6,11 +6,7 @@
 describe( 'Deft.mvc.ViewController', ->

 	hasListener = ( observable, eventName ) ->
-		# Ext JS's implementation of `Ext.util.Observable::hasListener()` returns inaccurate information after `Ext.util.Observable::clearListeners()` is called.
-		if ( observable.hasListener( eventName ) or observable.events[ eventName ]?.listeners?.length > 0 )
-			return true
-		else
-			return false
+		!!observable.hasListener( eventName )

 	describe( 'Configuration', ->


Fixing Typo

Fixing Typo

--- src/device.module.connection.coffee
+++ src/device.module.connection.coffee
@@ -11,9 +11,13 @@

 Device.Connection = do (dvc = Device) ->

-  online = (callback) -> dvc.addEvent window, "online", callback
+  EVENT =
+    ONLINE:   "online"
+    OFFLINE:  "offline"

-  offline = (callback) -> dvc.addEvent window, "offline", callback
+  online = (callback) -> dvc.addEvent window, EVENT.ONLINE, callback
+
+  offline = (callback) -> dvc.addEvent window, EVENT.OFFLINE, callback

   online: online
   offline: offline


func to testing if plan is at least partially open

--- tutor/src/helpers/plan.coffee
+++ tutor/src/helpers/plan.coffee
@@ -2,7 +2,7 @@
 _ = require 'underscore'

 {PlanPublishStore, PlanPublishActions} = require '../flux/plan-publish'
-{TimeStore, TimeActions} = require '../flux/time'
+{TimeStore} = require '../flux/time'

 PlanHelper =
   isPublishing: (plan) ->
@@ -27,4 +27,12 @@
     {isPublishing, publishStatus}

+  isPlanOpen: (plan) ->
+    now = moment(TimeStore.getNow())
+    for tasking in plan.tasking_plans
+      if moment(tasking.opens_at).isBefore(now)
+        return true # at least one tasking is opened
+    false
+
+
 module.exports = PlanHelper


cooldown finilazation

cooldown finilazation

--- src/cooldown.coffee
+++ src/cooldown.coffee
@@ -6,7 +6,8 @@
 player = GameObject
   health: 100

-player.cooldown "health",
+player.cooldown "health",
+

 # => `velocity is {x: 0, y: 0} and position is {x: 0, y: 0}`


rtp: Add functions to parse RTP/RTCP packet

--- rtp.coffee
+++ rtp.coffee
@@ -9,6 +9,8 @@
 # TODO: Use DON (decoding order number) to carry B-frames.
 #       DON is to RTP what DTS is to MPEG-TS.

+bits = require './bits'
+
 # Number of seconds from 1900-01-01 to 1970-01-01
 EPOCH = 2208988800

@@ -16,6 +18,7 @@
 NTP_SCALE_FRAC = 4294.967295
 TIMESTAMP_ROUNDOFF = 4294967296  # 32 bits

+# Minimum length of an RTP header
 RTP_HEADER_LEN = 12

 MAX_PAYLOAD_SIZE = 1360
@@ -23,6 +26,241 @@
 api =
   # Number of bytes in RTP header
   RTP_HEADER_LEN: RTP_HEADER_LEN
+
+  RTCP_PACKET_TYPE_SENDER_REPORT      : 200  # SR
+  RTCP_PACKET_TYPE_RECEIVER_REPORT    : 201  # RR
+  RTCP_PACKET_TYPE_SOURCE_DESCRIPTION : 202  # SDES
+  RTCP_PACKET_TYPE_GOODBYE            : 203  # BYE
+  RTCP_PACKET_TYPE_APPLICATION_DEFINED: 204  # APP
+
+  readRTCPSenderReport: ->
+    # RFC 3550 - 6.4.1 SR: Sender Report RTCP Packet
+    startBytePos = bits.current_position().byte
+    info = {}
+    info.version = bits.read_bits 2
+    info.padding = bits.read_bit()
+    info.reportCount = bits.read_bits 5
+    info.payloadType = bits.read_byte()  # == 200
+    if info.payloadType isnt api.RTCP_PACKET_TYPE_SENDER_REPORT
+      throw new Error "payload type must be #{api.RTCP_PACKET_TYPE_SENDER_REPORT}"
+    info.wordsMinusOne = bits.read_bits 16
+    info.totalBytes = (info.wordsMinusOne + 1) * 4
+    info.ssrc = bits.read_bits 32
+    info.ntpTimestamp = bits.read_bits 64  # loses some precision
+    info.rtpTimestamp = bits.read_bits 32
+    info.senderPacketCount = bits.read_bits 32
+    info.senderOctetCount = bits.read_bits 32
+
+    info.reportBlocks = []
+    for i in [0...info.reportCount]
+      reportBlock = {}
+      reportBlock.ssrc = bits.read_bits 32
+      reportBlock.fractionLost = bits.read_byte()
+      reportBlock.packetsLost = bits.read_int 24
+      reportBlock.highestSequenceNumber = bits.read_bits 32
+      reportBlock.jitter = bits.read_bits 32
+      reportBlock.lastSR = bits.read_bits 32
+      reportBlock.delaySinceLastSR = bits.read_bits 32
+      info.reportBlocks.push reportBlock
+
+    # skip padding bytes
+    readBytes = bits.current_position().byte - startBytePos
+    if readBytes < info.totalBytes
+      bits.skip_bytes info.totalBytes - readBytes
+
+    return info
+
+  readRTCPReceiverReport: ->
+    # RFC 3550 - 6.4.2 RR: Receiver Report RTCP Packet
+    startBytePos = bits.current_position().byte
+    info = {}
+    info.version = bits.read_bits 2
+    info.padding = bits.read_bit()
+    info.reportCount = bits.read_bits 5
+    info.payloadType = bits.read_byte()  # == 201
+    if info.payloadType isnt api.RTCP_PACKET_TYPE_RECEIVER_REPORT
+      throw new Error "payload type must be #{api.RTCP_PACKET_TYPE_RECEIVER_REPORT}"
+    info.wordsMinusOne = bits.read_bits 16
+    info.totalBytes = (info.wordsMinusOne + 1) * 4
+    info.ssrc = bits.read_bits 32
+    info.reportBlocks = []
+    for i in [0...info.reportCount]
+      reportBlock = {}
+      reportBlock.ssrc = bits.read_bits 32
+      reportBlock.fractionLost = bits.read_byte()
+      reportBlock.packetsLost = bits.read_int 24
+      reportBlock.highestSequenceNumber = bits.read_bits 32
+      reportBlock.jitter = bits.read_bits 32
+      reportBlock.lastSR = bits.read_bits 32
+      reportBlock.delaySinceLastSR = bits.read_bits 32
+      info.reportBlocks.push reportBlock
+
+    # skip padding bytes
+    readBytes = bits.current_position().byte - startBytePos
+    if readBytes < info.totalBytes
+      bits.skip_bytes info.totalBytes - readBytes
+
+    return info
+
+  readRTCPSourceDescription: ->
+    # RFC 3550 - 6.5 SDES: Source Description RTCP Packet
+    startBytePos = bits.current_position().byte
+    info = {}
+    info.version = bits.read_bits 2
+    info.padding = bits.read_bit()
+    info.sourceCount = bits.read_bits 5
+    info.payloadType = bits.read_byte()  # == 202
+    if info.payloadType isnt api.RTCP_PACKET_TYPE_SOURCE_DESCRIPTION
+      throw new Error "payload type must be #{api.RTCP_PACKET_TYPE_SOURCE_DESCRIPTION}"
+    info.wordsMinusOne = bits.read_bits 16
+    info.totalBytes = (info.wordsMinusOne + 1) * 4
+    info.chunks = []
+    for i in [0...info.sourceCount]
+      chunk = {}
+      chunk.ssrc_csrc = bits.read_bits 32
+      chunk.sdesItems = []
+      chunk.sdes = {}
+      moreSDESItems = true
+      while moreSDESItems
+        sdesItem = {}
+        sdesItem.type = bits.read_byte()
+        sdesItem.octetCount = bits.read_byte()
+        if sdesItem.octetCount > 255
+          throw new Error "octet count too large: #{sdesItem.octetCount} <= 255"
+        sdesItem.text = bits.read_bytes(sdesItem.octetCount).toString 'utf8'
+        switch sdesItem.type
+          when 0  # terminate the list
+            # skip until the next 32-bit boundary
+            bytesPastBoundary = (bits.current_position().byte - startBytePos) % 4
+            if bytesPastBoundary > 0
+              while bytesPastBoundary < 4
+                nullOctet = bits.read_byte()
+                if nullOctet isnt 0x00
+                  throw new Error "padding octet must be 0x00: #{nullOctet}"
+                bytesPastBoundary++
+
+            moreSDESItems = false  # end the loop
+          when 1  # Canonical End-Point Identifier
+            chunk.sdes.cname = sdesItem.text
+          when 2  # User Name
+            chunk.sdes.name = sdesItem.text
+          when 3  # Electronic Mail Address
+            chunk.sdes.email = sdesItem.text
+          when 4  # Phone Number
+            chunk.sdes.phone = sdesItem.text
+          when 5  # Geographic User Location
+            chunk.sdes.loc = sdesItem.text
+          when 6  # Application or Tool Name
+            chunk.sdes.tool = sdesItem.text
+          when 7  # Notice/Status
+            chunk.sdes.note = sdesItem.text
+          when 8  # Private Extensions
+            chunk.sdes.priv = sdesItem.text
+          else
+            throw new Error "unknown SDES item type in source description " +
+              "RTCP packet: #{chunk.type} (maybe not implemented yet)"
+        chunk.sdesItems.push sdesItem
+      info.chunks.push chunk
+
+    # skip padding bytes
+    readBytes = bits.current_position().byte - startBytePos
+    if readBytes < info.totalBytes
+      bits.skip_bytes info.totalBytes - readBytes
+
+    return info
+
+  readRTCPGoodbye: ->
+    # RFC 3550 - 6.6 BYE: Goodbye RTCP Packet
+    startBytePos = bits.current_position().byte
+    info = {}
+    info.version = bits.read_bits 2
+    info.padding = bits.read_bit()
+    info.sourceCount = bits.read_bits 5
+    info.payloadType = bits.read_byte()  # == 203
+    if info.payloadType isnt api.RTCP_PACKET_TYPE_GOODBYE
+      throw new Error "payload type must be #{api.RTCP_PACKET_TYPE_GOODBYE}"
+    info.wordsMinusOne = bits.read_bits 16
+    info.totalBytes = (info.wordsMinusOne + 1) * 4
+    info.ssrc = bits.read_bits 32
+
+    if bits.has_more_data()
+      info.reasonOctetCount = bits.read_byte()
+      reason = bits.read_bytes info.reasonOctetCount
+
+    # skip padding bytes
+    readBytes = bits.current_position().byte - startBytePos
+    if readBytes < info.totalBytes
+      bits.skip_bytes info.totalBytes - readBytes
+
+    return info
+
+  readRTCPApplicationDefined: ->
+    # RFC 3550 - 6.7 APP: Application-Defined RTCP Packet
+    startBytePos = bits.current_position().byte
+    info = {}
+    info.version = bits.read_bits 2
+    info.padding = bits.read_bit()
+    info.subtype = bits.read_bits 5
+    info.payloadType = bits.read_byte()  # == 204
+    if info.payloadType isnt api.RTCP_PACKET_TYPE_APPLICATION_DEFINED
+      throw new Error "payload type must be #{api.RTCP_PACKET_TYPE_APPLICATION_DEFINED}"
+    info.wordsMinusOne = bits.read_bits 16
+    info.totalBytes = (info.wordsMinusOne + 1) * 4
+    info.ssrc_csrc = bits.read_bits 32
+    info.name = bits.read_bytes(4).toString 'ascii'
+
+    # read the application-dependent data (remaining bytes)
+    readBytes = bits.current_position().byte - startBytePos
+    if readBytes < info.totalBytes
+      info.applicationData = bits.read_bytes info.totalBytes - readBytes
+    else
+      info.applicationData = null
+
+    return info
+
+  readRTPFixedHeader: ->
+    # RFC 3550 - 5.1 RTP Fixed Header Fields
+    info = {}
+    info.version = bits.read_bits 2
+    info.padding = bits.read_bit()
+    info.extension = bits.read_bit()
+    info.csrcCount = bits.read_bits 4
+    info.marker = bits.read_bit()
+    info.payloadType = bits.read_bits 7
+    info.sequenceNumber = bits.read_bits 16
+    info.timestamp = bits.read_bits 32
+    info.ssrc = bits.read_bits 32
+    info.csrc = []
+    for i in [0...info.csrcCount]
+      info.csrc.push bits.read_bits 32
+    return info
+
+  parsePackets: (buf) ->
+    bits.push_stash()
+    bits.set_data buf
+
+    packets = []
+    while bits.has_more_data()
+      packet = {}
+      packet.payloadValue = bits.get_byte_at 1
+      switch packet.payloadValue
+        when api.RTCP_PACKET_TYPE_SENDER_REPORT
+          packet.rtcpSenderReport = api.readRTCPSenderReport()
+        when api.RTCP_PACKET_TYPE_RECEIVER_REPORT
+          packet.rtcpReceiverReport = api.readRTCPReceiverReport()
+        when api.RTCP_PACKET_TYPE_SOURCE_DESCRIPTION
+          packet.rtcpSourceDescription = api.readRTCPSourceDescription()
+        when api.RTCP_PACKET_TYPE_GOODBYE
+          packet.rtcpGoodbye = api.readRTCPGoodbye()
+        when api.RTCP_PACKET_TYPE_APPLICATION_DEFINED
+          packet.rtcpApplicationDefined = api.readRTCPApplicationDefined()
+        else  # RTP data transfer protocol - fixed header
+          packet.rtpHeader = api.readRTPFixedHeader()
+      packets.push packet
+
+    bits.pop_stash()
+
+    return packets

   # Replace SSRC in-place in the given RTP header
   replaceSSRCInRTP: (buf, ssrc) ->


Fix trailing space

Fix trailing space

--- src/flowdock.coffee
+++ src/flowdock.coffee
@@ -108,7 +108,7 @@

   # Invite a user to an organization's flow
   invite: (flowId, organizationId, email, message, callback) ->
-    data =
+    data =
       email: email
       message: message
     @send "/flows/#{organizationId}/#{flowId}/invitations", data, callback


Fix user pattern with new Slack adapter

Fix user pattern with new Slack adapter

--- src/highfive.coffee
+++ src/highfive.coffee
@@ -24,9 +24,11 @@
     # Services for getting emails from users
     email_fetchers =
         slack: (username1, username2, callback) ->
+            # Slack decorates some usernames
+            [username1,username2] = (u.replace(/^[<@]*|[>]$/gm, '') for u in [username1, username2])
             new SlackApp(robot).listUsers (resp) ->
                 grabber = (name) ->
-                    (x for x in resp.members when x.name == name)[0]?.profile.email
+                    (x for x in resp.members when name in [x.name, x.id])[0]?.profile.email
                 return callback(null, null) unless resp.members?
                 [e1, e2] = (grabber(u) for u in [username1, username2])
                 callback e1, e2
@@ -88,7 +90,7 @@
             """

     # The main responder
-    robot.respond /highfive (@\S+)( \$(\d+))? for (.*)/, (msg) ->
+    robot.respond /highfive (.+?)( \$(\d+))? for (.*)/, (msg) ->
         from_user = msg.message.user.name
         to_user = msg.match[1][1..]
         amt = parseInt(msg.match[3] or 0)
@@ -144,7 +146,7 @@
                 sendCard = ->
                     message = "High five for #{reason}!"
                     tango.orderAmazonDotComCard cust, acct, 'High-five', amt*100, from_user, 'High Five!', to_user, to_email, message, (resp) ->
-                        debug msg, "order response `#{resp}`"
+                        debug msg, "order response `#{JSON.stringify resp}`"
                         unless resp.success
                             errmsg = resp.invalid_inputs_message || resp.error_message || resp.denial_message
                             return msg.send "(Problem ordering gift card: '#{errmsg}'. You might want 'highfive config'.)"


Added limitation to zoom level to allow zooming only up to to seconds

--- src/d3.timeslider.coffee
+++ src/d3.timeslider.coffee
@@ -242,11 +242,15 @@

         # create the zoom behavior
         minScale = (@options.display.start - @options.display.end) / (@options.domain.start - @options.domain.end)
+        # Calculate maxScale by gettting milliseconds difference  of the displayed
+        # time domain (getting the seconds by dividing by 1000) and halving it.
+        # This should allow to zoom into to see up to two seconds in the complete timeslider
+        maxScale = Math.abs(@options.display.start - @options.display.end)/2000

         @options.zoom = d3.behavior.zoom()
             .x(@scales.x)
             .size([@options.width, @options.height])
-            .scaleExtent([minScale, Infinity])
+            .scaleExtent([minScale, maxScale])
             .on('zoomstart', =>
                 @prevScale2 = @options.zoom.scale()
                 @prevDomain = @scales.x.domain()


Refactor and improve endpoint specs

Refactor and improve endpoint specs

--- src/endpoint.spec.coffee
+++ src/endpoint.spec.coffee
@@ -18,7 +18,7 @@

   describe '#initialize', ->

-    it 'should register the a connection handler on the Socket.IO server', ->
+    it 'should register a connection handler on the Socket.IO server', ->
       endpoint.initialize
         ioInstance: ioStub

@@ -37,12 +37,10 @@
       socketStub.on.withArgs('eventric:joinRoom').yields 'RoomName'

-    describe 'given no rpc request middleware', ->
-
-      it 'should join the room', ->
-        endpoint.initialize
-          ioInstance: ioStub
-        expect(socketStub.join).to.have.been.calledWith 'RoomName'
+    it 'should join the room', ->
+      endpoint.initialize
+        ioInstance: ioStub
+      expect(socketStub.join).to.have.been.calledWith 'RoomName'

     describe 'given a rpc request middleware', ->
@@ -52,58 +50,48 @@
       beforeEach ->
         rpcRequestMiddlewareStub = sandbox.stub()

-      describe 'always', ->
-
-        it 'should call the middleware and pass in the room name and the assiocated socket', (done) ->
-          rpcRequestMiddlewareStub.returns new Promise (resolve) -> resolve()
-          endpoint.initialize
-            ioInstance: ioStub
-            rpcRequestMiddleware: rpcRequestMiddlewareStub
-
-          setTimeout ->
-            expect(rpcRequestMiddlewareStub).to.have.been.called
-            expect(rpcRequestMiddlewareStub.firstCall.args[0]).to.equal 'RoomName'
-            expect(rpcRequestMiddlewareStub.firstCall.args[1]).to.equal socketStub
-            done()
-
-
-      describe 'which resolves', ->
-
-        it 'should join the room', (done) ->
-          rpcRequestMiddlewareStub.returns new Promise (resolve) -> resolve()
-          endpoint.initialize
-            ioInstance: ioStub
-            rpcRequestMiddleware: rpcRequestMiddlewareStub
-          setTimeout ->
-            expect(socketStub.join).to.have.been.calledWith 'RoomName'
-            done()
-
-
-      describe 'which rejects', ->
-
-        it 'should not join the room', (done) ->
-          rpcRequestMiddlewareStub.returns new Promise (resolve, reject) -> reject()
-          endpoint.initialize
-            ioInstance: ioStub
-            rpcRequestMiddleware: rpcRequestMiddlewareStub
-          setTimeout ->
-            expect(socketStub.join.calledOnce).to.be.false
-            done()
+
+      it 'should call the middleware and pass in the room name and the assiocated socket', (done) ->
+        rpcRequestMiddlewareStub.returns new Promise (resolve) -> resolve()
+        endpoint.initialize
+          ioInstance: ioStub
+          rpcRequestMiddleware: rpcRequestMiddlewareStub
+
+        setTimeout ->
+          expect(rpcRequestMiddlewareStub).to.have.been.called
+          expect(rpcRequestMiddlewareStub.firstCall.args[0]).to.equal 'RoomName'
+          expect(rpcRequestMiddlewareStub.firstCall.args[1]).to.equal socketStub
+          done()
+
+
+      it 'should join the room given the middleware resolves', (done) ->
+        rpcRequestMiddlewareStub.returns Promise.resolve()
+        endpoint.initialize
+          ioInstance: ioStub
+          rpcRequestMiddleware: rpcRequestMiddlewareStub
+        setTimeout ->
+          expect(socketStub.join).to.have.been.calledWith 'RoomName'
+          done()
+
+
+      it 'should not join the room given the middleware rejects', (done) ->
+        rpcRequestMiddlewareStub.returns Promise.reject()
+        endpoint.initialize
+          ioInstance: ioStub
+          rpcRequestMiddleware: rpcRequestMiddlewareStub
+        setTimeout ->
+          expect(socketStub.join.calledOnce).to.be.false
+          done()

   describe 'receiving an eventric:leaveRoom socket event', ->

-    socketStub = null
-
-    beforeEach ->
+    it 'should leave the room', ->
       socketStub =
         on: sandbox.stub()
         join: sandbox.stub()
         leave: sandbox.stub()
       ioStub.sockets.on.withArgs('connection').yields socketStub
-
-
-    it 'should leave the room', ->
       socketStub.on.withArgs('eventric:leaveRoom').yields 'RoomName'
       endpoint.initialize
         ioInstance: ioStub
@@ -127,97 +115,113 @@
       endpoint.setRPCHandler rpcHandlerStub

-    describe 'given no rpc request middleware', ->
+    it 'should execute the configured rpc handler', ->
+      endpoint.initialize
+        ioInstance: ioStub
+      expect(rpcHandlerStub).to.have.been.calledWith rpcRequestFake, sinon.match.func
+
+
+    it 'should emit the return value of the configured handler as eventric:rpcResponse', ->
+      responseFake = {}
+      rpcHandlerStub.yields null, responseFake
+      endpoint.initialize
+        ioInstance: ioStub
+      expect(socketStub.emit).to.have.been.calledWith 'eventric:rpcResponse',
+        rpcId: rpcRequestFake.rpcId
+        error: null
+        data: responseFake
+
+
+    it 'should call the middleware with the rpc request data and the assiocated socket given a rpc request middleware', (done) ->
+      rpcRequestMiddlewareStub = sandbox.stub()
+      rpcRequestMiddlewareStub.returns new Promise (resolve) -> resolve()
+      endpoint.initialize
+        ioInstance: ioStub
+        rpcRequestMiddleware: rpcRequestMiddlewareStub
+
+      setTimeout ->
+        expect(rpcRequestMiddlewareStub).to.have.been.called
+        expect(rpcRequestMiddlewareStub.firstCall.args[0]).to.equal rpcRequestFake
+        expect(rpcRequestMiddlewareStub.firstCall.args[1]).to.equal socketStub
+        done()
+
+
+    describe 'given the configured rpc handler rejects', ->
+
+      it 'should emit the an eventric:rpcResponse event with an error', ->
+        error = new Error 'Custom error'
+        rpcHandlerStub.yields error, null
+        endpoint.initialize
+          ioInstance: ioStub
+        expect(socketStub.emit).to.have.been.calledWith 'eventric:rpcResponse',
+          rpcId: rpcRequestFake.rpcId
+          error: sinon.match.has 'message', 'Custom error'
+          data: null
+
+
+      it 'should emit the an eventric:rpcResponse event with a serializable error object', ->
+        error = new Error 'The error message'
+        rpcHandlerStub.yields error, null
+        endpoint.initialize
+          ioInstance: ioStub
+        receivedError = socketStub.emit.getCall(0).args[1].error
+        expect(receivedError).to.be.an.instanceOf Object
+        expect(receivedError).not.to.be.an.instanceOf Error
+
+
+    describe 'given a rpc request middleware which resolves', ->
+
+      rpcRequestMiddlewareStub = null
+      responseFake = null
+
+
+      beforeEach (done) ->
+        rpcRequestMiddlewareStub = sandbox.stub()
+        responseFake = {}
+        rpcRequestMiddlewareStub.returns new Promise (resolve) -> resolve()
+        rpcHandlerStub.yields null, responseFake
+        endpoint.initialize
+          ioInstance: ioStub
+          rpcRequestMiddleware: rpcRequestMiddlewareStub
+        setTimeout ->
+          done()
+

       it 'should execute the configured rpc handler', ->
-        endpoint.initialize
-          ioInstance: ioStub
         expect(rpcHandlerStub.calledWith rpcRequestFake, sinon.match.func).to.be.ok

       it 'should emit the return value of the configured handler as eventric:rpcResponse', ->
-        responseFake = {}
-        rpcHandlerStub.yields null, responseFake
-        endpoint.initialize
-          ioInstance: ioStub
-        expect(
-          socketStub.emit.calledWith 'eventric:rpcResponse', rpcId: rpcRequestFake.rpcId, error: null, data: responseFake
-        ).to.be.ok
-
-
-    describe 'given a rpc request middleware', ->
+        expect(socketStub.emit.calledWith 'eventric:rpcResponse', rpcId: rpcRequestFake.rpcId, error: null, data: responseFake).to.be.ok
+
+
+    describe 'given a rpc request middleware which rejects', ->

       rpcRequestMiddlewareStub = null

-      beforeEach ->
+      beforeEach (done) ->
         rpcRequestMiddlewareStub = sandbox.stub()
-
-
-      describe 'always', ->
-
-        it 'should call the middleware and pass in the rpc request data and the assiocated socket', (done) ->
-          rpcRequestMiddlewareStub.returns new Promise (resolve) -> resolve()
-          endpoint.initialize
-            ioInstance: ioStub
-            rpcRequestMiddleware: rpcRequestMiddlewareStub
-
-          setTimeout ->
-            expect(rpcRequestMiddlewareStub).to.have.been.called
-            expect(rpcRequestMiddlewareStub.firstCall.args[0]).to.equal rpcRequestFake
-            expect(rpcRequestMiddlewareStub.firstCall.args[1]).to.equal socketStub
-            done()
-
-
-      describe 'which resolves', ->
-
-        responseFake = null
-
-        beforeEach (done) ->
-          responseFake = {}
-          rpcRequestMiddlewareStub.returns new Promise (resolve) -> resolve()
-          rpcHandlerStub.yields null, responseFake
-          endpoint.initialize
-            ioInstance: ioStub
-            rpcRequestMiddleware: rpcRequestMiddlewareStub
-          setTimeout ->
-            done()
-
-
-        it 'should execute the configured rpc handler', ->
-          expect(rpcHandlerStub.calledWith rpcRequestFake, sinon.match.func).to.be.ok
-
-
-        it 'should emit the return value of the configured handler as eventric:rpcResponse', ->
-          expect(socketStub.emit.calledWith 'eventric:rpcResponse', rpcId: rpcRequestFake.rpcId, error: null, data: responseFake).to.be.ok
-
-
-      describe 'which rejects', ->
-
-        responseFake = null
-
-        beforeEach (done) ->
-          responseFake = {}
-          errorFake = new Error 'The error message'
-          rpcRequestMiddlewareStub.returns new Promise (resolve, reject) -> reject errorFake
-          rpcHandlerStub.yields null, responseFake
-          endpoint.initialize
-            ioInstance: ioStub
-            rpcRequestMiddleware: rpcRequestMiddlewareStub
-          setTimeout ->
-            done()
-
-
-        it 'should not execute the configured rpc handler', ->
-          expect(rpcHandlerStub).to.not.have.been.calledWith rpcRequestFake, sinon.match.func
-
-
-        it 'should emit a eventric:rpcResponse event with a converted error object', ->
-          expect(socketStub.emit).to.have.been.calledWith(
-              'eventric:rpcResponse'
-                rpcId: rpcRequestFake.rpcId
-                error: message: 'The error message', name: 'Error'
-                data: null
-            )
+        error = new Error 'The error message'
+        rpcRequestMiddlewareStub.returns Promise.reject error
+        rpcHandlerStub.yields null, {}
+        endpoint.initialize
+          ioInstance: ioStub
+          rpcRequestMiddleware: rpcRequestMiddlewareStub
+        setTimeout ->
+          done()
+
+
+      it 'should not execute the configured rpc handler', ->
+        expect(rpcHandlerStub).to.not.have.been.calledWith rpcRequestFake, sinon.match.func
+
+
+      it 'should emit an eventric:rpcResponse event with an error object', ->
+        expect(socketStub.emit).to.have.been.calledWith(
+            'eventric:rpcResponse'
+              rpcId: rpcRequestFake.rpcId
+              error: sinon.match.object
+              data: null
+          )

   describe '#publish', ->
@@ -232,31 +236,25 @@

       ioStub.to = sandbox.stub().returns channelStub

-    describe 'given only a context name', ->
-
-      it 'should emit an event with payload to the correct channel', ->
-        payload = {}
-        endpoint.publish 'context', payload
-
-        expect(ioStub.to).to.have.been.calledWith 'context'
-        expect(channelStub.emit).to.have.been.calledWith 'context', payload
-
-
-    describe 'given a context name and event name', ->
-
-      it 'should emit an event with payload to the correct channel', ->
-        payload = {}
-        endpoint.publish 'context', 'EventName', payload
-
-        expect(ioStub.to).to.have.been.calledWith 'context/EventName'
-        expect(channelStub.emit).to.have.been.calledWith 'context/EventName', payload
-
-
-    describe 'given a context name, event name and aggregate id', ->
-
-      it 'should emit an event with payload to the correct channel', ->
-        payload = {}
-        endpoint.publish 'context', 'EventName', '12345', payload
-
-        expect(ioStub.to).to.have.been.calledWith 'context/EventName/12345'
-        expect(channelStub.emit).to.have.been.calledWith 'context/EventName/12345', payload
+    it 'should emit an event with payload to the correct channel given only a context name', ->
+      payload = {}
+      endpoint.publish 'context', payload
+
+      expect(ioStub.to).to.have.been.calledWith 'context'
+      expect(channelStub.emit).to.have.been.calledWith 'context', payload
+
+
+    it 'should emit an event with payload to the correct channel given a context name and event name', ->
+      payload = {}
+      endpoint.publish 'context', 'EventName', payload
+
+      expect(ioStub.to).to.have.been.calledWith 'context/EventName'
+      expect(channelStub.emit).to.have.been.calledWith 'context/EventName', payload
+
+
+    it 'should emit an event with payload to the correct channel given a context name, event name and aggregate id', ->
+      payload = {}
+      endpoint.publish 'context', 'EventName', '12345', payload
+
+      expect(ioStub.to).to.have.been.calledWith 'context/EventName/12345'
+      expect(channelStub.emit).to.have.been.calledWith 'context/EventName/12345', payload


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/engine_levels.coffee
+++ src/engine_levels.coffee
@@ -1,9 +1,11 @@
 ###*
-The Game Over class sets up a simple game state with restart instructions.
+This module clears or fills the canvas before drawing the scene.

-@see TextScreen
-@name GameOver
-@constructor
+@name Clear
+@fieldOf Engine
+@module
+@param {Object} I Instance variables
+@param {Object} self Reference to the engine
 ###
 Engine.Levels = (I, self) ->
   Object.reverseMerge I,


always return an array

always return an array

--- src/flux/exercise.coffee
+++ src/flux/exercise.coffee
@@ -154,7 +154,7 @@
       _.groupBy(includedExercises, getChapterSection)

     groupBySectionsAndTypes: (pageIds) ->
-      all = @_exercises[pageIds.toString()]
+      all = @_exercises[pageIds.toString()] or []
       results = {
         all:
           count: all.length


fix run.coffee

fix run.coffee

--- run.coffee
+++ run.coffee
@@ -10,7 +10,7 @@
 {translate} = require './translator'

 argv = require('minimist')(process.argv.slice(2))
-input = argv._[0].trim()
+input = argv._[0].toString().trim()
 if !input
   console.error """
     -i    print input


Working With Admin

Working With Admin

--- app/assets/javascripts/app.js.coffee
+++ app/assets/javascripts/app.js.coffee
@@ -24,4 +24,6 @@
 				$.each data, (i,v) =>
 					if v.player && v.player.id > 0
 						if v.team && v.team.id > 0
-							$('#gb-'+v.id).html(v.player.name + ' / ' + v.team.name )+							$('#gb-'+v.id).html(v.player.name + ' / ' + v.team.name )
+						else
+							$('#gb-'+v.id).html('')


Prevent possible FOUC in preview

Prevent possible FOUC in preview

--- app/assets/javascripts/themer.coffee
+++ app/assets/javascripts/themer.coffee
@@ -53,8 +53,11 @@
   updatePreview: ->
     $head = @$viewport.contents().find('head').first()
     $ajax = $.post '/theme/preview.css', @$form.serialize(), (data) =>
-      $head.find('style#preview').remove()
-      $head.append("<style id=\"preview\">#{data}</style>")
+      $stylesheet = $head.find('style#preview').first()
+      if $stylesheet.length >= 1
+        $stylesheet.html(data)
+      else
+        $head.append("<style id=\"preview\">#{data}</style>")

   init: ->
     @$colorPickers.blur @colorChanged


compression et décompression du path

compression et décompression du path

--- app/assets/jax/models/path.js.coffee
+++ app/assets/jax/models/path.js.coffee
@@ -77,7 +77,7 @@
   ###
     Print uncompressed path of this object
   ###
-  print_uncompressed: ->
+  print_uncompressed: ->
     line = ""
     for move in @moves
       line = line + move
@@ -87,7 +87,8 @@
     Print compressed path of this object
   ###
   print_compressed: ->
-    compressed_moves = @compress_path(@moves)
+    compressed_moves = @compress_path(@moves)
+
     line = ""
     for move in compressed_moves
       line = line + move
@@ -134,39 +135,42 @@

     return compressed_path

-###
-  Uncompress a path (see description of Path class)
-  @param path compressed path
-  @return uncompressed path
-###
+  ###
+    Uncompress a path (see description of Path class)
+    @param path compressed path
+    @return uncompressed path
+  ###
   uncompress_path: (compressed_path) ->
-   i = 0
-   j = 0
-   buffer = []
-   cpt = 1
-   uncompressed_path = []
-
-   # While we're not in the end of compressed path
-   while compressed_path[i] != '\0'
-     buffer[0] = '1'
-     buffer[1] = '\0'
-
-     # we put decimal in buffer
-     while Util::isDecimal(compressed_path[i])
-       buffer[j] = compressedPath[i]
-       buffer[j+1] = '\0'
-       j = j + 1
-       i = i + 1
-
-     # we get decimal on untextual mode
-     nbr = atoi(buffer)
-
-     for(j = 0 ; j < nbr ; j++)
-       cpt = cpt + 1
-       uncompressed_path[cpt-2] = compressed_path[i]
-       uncompressed_path[cpt-1] = '\0'
-
-     i = i + 1
-     j = 0
-
-   return uncompressed_path
+    i = 0
+    uncompressed_path = []
+
+    # While we're not in the end of compressed path
+    while i < compressed_path.length
+      cell = compressed_path[i]
+      nbr_buffer = []
+
+      # if not decimal, only 1 move/push
+      if cell < '0' or cell > '9'
+        nbr_buffer.push('1')
+
+      # if decimal, we put it in buffer to create a number like ['1', '2'] for 12
+      while cell >= '0' and cell <= '9'
+        nbr_buffer.push(cell)
+        i = i + 1
+        cell = compressed_path[i]
+
+      # we transform the array on string...
+      nbr_string = ""
+      for letter in nbr_buffer
+        nbr_string = nbr_string + "#{letter}"
+
+      # ... and then convert the string on integer
+      nbr = parseInt(nbr_string)
+
+      # write nbr times the move/push
+      for j in [0..nbr-1]
+        uncompressed_path.push(compressed_path[i])
+
+      i = i + 1
+
+    return uncompressed_path


Fetching subjects one at a time to distribute groups

--- app/models/galaxy_zoo_subject.coffee
+++ app/models/galaxy_zoo_subject.coffee
@@ -3,6 +3,7 @@
 GalaxyZooSurveyGroup = require 'models/galaxy_zoo_survey_group'
 SloanTree = require 'lib/sloan_tree'
 CandelsTree = require 'lib/candels_tree'
+_ = require 'underscore/underscore'

 class GalaxyZooSubject extends Subject
   @configure 'GalaxyZooSubject', 'zooniverse_id', 'coords', 'location', 'metadata'
@@ -31,7 +32,8 @@

   @fetch: ->
     count = Config.subjectCache - @count()
-    super count
+    _(super).tap =>
+      _(count - 1).times => super(1)

   constructor: ->
     super


correcting collection url

correcting collection url

--- apps/partner/client/articles.coffee
+++ apps/partner/client/articles.coffee
@@ -5,7 +5,7 @@
 module.exports = class ArticlesAdapter
   constructor: ({ profile, partner, cache, el }) ->
     collection = new Articles
-    collection.url = "#{collection.url}/api/articles/?partner_id=#{partner.get('_id')}&published=true&limit=5&sort=-published_at"
+    collection.url = "#{collection.url}/?partner_id=#{partner.get('_id')}&published=true&limit=5&sort=-published_at"
     view = new ArticlesFeedView el: el, collection: collection
     el.html '

'
     el.addClass view.className


Fix typo

Fix typo

--- webpack/index.coffee
+++ webpack/index.coffee
@@ -10,11 +10,11 @@
 stringify = (rec)->
   res = {}
   for k, v of rec
-    rec[k] = if 'string'==typeof v
+    res[k] = if 'string'==typeof v
       JSON.stringify v
     else
       v
-  rec
+  res

 @entry = "./src"


force node names internally to be consistently upper-case

--- dom.coffee
+++ dom.coffee
@@ -748,7 +748,7 @@
 		if not nodeClass?
 			new ELEMENT_MAP['_'](name.toUpperCase())
 		else
-			new nodeClass(null,null,null,@)
+			new nodeClass(name.toUpperCase(),null,null,@)
 	# createEntityReference: NotSupported
 	createEvent: (type) ->
 		switch type


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/array_extensions.coffee
+++ src/array_extensions.coffee
@@ -119,7 +119,16 @@

 ###*
 Remove the first occurrence of the given object from the array if it is
-present.
+present. The array is modified in place.
+
+

+   a = [1, 1, "a", "b"]
+   a.remove(1)
+=> 1
+
+   a
+=> [1, "a", "b"]
+

 @name remove
 @methodOf Array#


Adding Stace Welsh to users.

Adding Stace Welsh to users.

--- assets/javascripts/views/username.coffee
+++ assets/javascripts/views/username.coffee
@@ -12,6 +12,7 @@
     swaffords:     "Scott Swafford"
     jenleereeves:  "Jen Lee Reeves"
     projectopenvault: "Project OpenVault Team"
+    stacewelsh:     "Stace Welsh"

   affiliations:
     knowtheory:    "Project Open Vault"
@@ -23,6 +24,7 @@
     scottphamkbia: "KBIA Radio"
     swaffords:     "The Columbia Missourian"
     jenleereeves:  "KOMU8"
+    stacewelsh:    "KOMU8"

   initialize: (options) ->
     @username = (options.username || options.el.html() || "").toLowerCase()


Also listen to 'open-file' event in constructor.

The 'open-file' may happen before application is launched.

--- src/atom-application.coffee
+++ src/atom-application.coffee
@@ -23,6 +23,15 @@
     @pidsToOpenWindows = {}
     @pathsToOpen ?= []
     @windows = []
+
+    app.on 'open-file', (event, filePath) =>
+      event.preventDefault()
+      if @launched
+        @openPath filePath
+      else
+        # Delay opening until Atom has finished launching, this condition
+        # happens when user double clicks a file in Finder to open it.
+        @pathsToOpen.push filePath

     app.on 'finish-launching', =>
       @launched = true
@@ -146,15 +155,6 @@
     app.on 'window-all-closed', ->
       app.quit()

-    app.on 'open-file', (event, filePath) =>
-      event.preventDefault()
-      if @launched
-        @openPath filePath
-      else
-        # Delay opening until Atom has finished launching, this condition
-        # happens when user double clicks a file in Finder to open it.
-        @pathsToOpen.push filePath
-
     ipc.on 'close-without-confirm', (processId, routingId) ->
       window = BrowserWindow.fromProcessIdAndRoutingId processId, routingId
       window.removeAllListeners 'close'


Use updateAvailable

Use updateAvailable

--- spec/about-spec.coffee
+++ spec/about-spec.coffee
@@ -277,8 +277,8 @@
       waits(1) # Service consumption hooks are deferred until the next tick
       runs -> expect(workspaceElement).not.toContain('.about-release-notes')

-triggerUpdate = (version) ->
-  MockUpdater.finishDownloadingUpdate(version)
+triggerUpdate = (releaseVersion) ->
+  atom.updateAvailable({releaseVersion})

 MockUpdater =
   checkForUpdate: ->


Send msg to logger.

Send msg to logger.

--- src/coffee/pricesync.coffee
+++ src/coffee/pricesync.coffee
@@ -237,15 +237,14 @@

   _logError: (msg) ->
     if @logger?
-      @logger.error error: msg
+      @logger.error error: msg, msg

   _logWarn: (msg) ->
     if @logger?
-      @logger.warn warn: msg
-
+      @logger.warn warn: msg, msg

   _logInfo: (msg) ->
     if @logger?
-      @logger.info info: msg
+      @logger.info info: msg, msg

 module.exports = PriceSync


change disable selector in published mode

--- BackofficeBundle/Resources/public/coffee/FullPageFormView.coffee
+++ BackofficeBundle/Resources/public/coffee/FullPageFormView.coffee
@@ -21,8 +21,6 @@
   render: ->
     $(@el).html(@renderTemplate('fullPageFormView', @options))
     $('.js-widget-title', @$el).html $('#generated-title', @$el).html()
-    if @options.multiStatus && @element.status_label == 'published'
-      $('[type="submit"]', @$el).addClass('disabled')
     @addEventOnForm()
     @addSelect2OnForm()
     $("[data-prototype]").each ->


removing useless file

removing useless file

--- toaster.coffee
+++ toaster.coffee
@@ -1,4 +0,0 @@
-modules =
-	name: 'Coffee Toaster'
-	src: 'src'
-	release: 'lib/toaster.js'


whitespace

whitespace

--- app/assets/javascripts/components/yale-about-page.cjsx
+++ app/assets/javascripts/components/yale-about-page.cjsx
@@ -25,7 +25,7 @@

Contact Us

-

+

                 Questions about Ensemble@Yale? Contact the DHLab: dhlab@yale.edu.
                 Questions about theater history at Yale? Start with an introductory research guide or contact the Arts librarian for theater and drama: lindsay.king@yale.edu.

@@ -34,7 +34,7 @@

-
+

Sponsors


Fix calculating input step when input max < min

Ref #4000

--- app/assets/javascripts/lib/models/input_element.coffee
+++ app/assets/javascripts/lib/models/input_element.coffee
@@ -125,8 +125,9 @@
     step = @get('step_value')
     delta = @get('max_value') - @get('min_value')

-    while (step * 20) > delta
-      step /= 10
+    if delta > 0
+      while (step * 20) > delta
+        step /= 10

     @smartStepValue = step


OX-248 lookup up depth is now unlmited

OX-248 lookup up depth is now unlmited

--- app/assets/javascripts/oxalis/model/binary/cube.coffee
+++ app/assets/javascripts/oxalis/model/binary/cube.coffee
@@ -8,7 +8,7 @@
   BUCKET_SIZE_P : 5
   BUCKET_LENGTH : 0
   ZOOM_STEP_COUNT : 0
-  LOOKUP_DEPTH_UP : 2
+  LOOKUP_DEPTH_UP : 0
   LOOKUP_DEPTH_DOWN : 1

   LOADING_PLACEHOLDER : {}
@@ -20,6 +20,7 @@

     _.extend(@, new EventMixin())

+    @LOOKUP_DEPTH_UP = @ZOOM_STEP_COUNT
     @BUCKET_LENGTH = 1 << @BUCKET_SIZE_P * 3
     @cube = []


Remove useless consistency checker

Remove useless consistency checker

--- app/src/wallet/wallet_setup_consistency_checker.coffee
+++ app/src/wallet/wallet_setup_consistency_checker.coffee
@@ -1,52 +0,0 @@
-
-ledger.wallet ?= {}
-
-_store = null
-store = -> _store ||= new ledger.storage.ChromeStore("wallet_setup_consistency_checker")
-
-class MemoryWallet
-
-  constructor: (seed) ->
-    @_masterNode = bitcoin.HDNode.fromSeedHex(seed.toString())
-
-  getPublicAddress: (path, callback) ->
-    _.defer => callback?(@getPublicAddressSync(path))
-
-  getPublicAddressSync: (path) ->
-    path = path.split('/')
-    node = @_masterNode
-    for item in path
-      [index, hardened] = item.split "'"
-      node  = if hardened? then node.deriveHardened parseInt(index) else node = node.derive index
-    bitcoinAddress: new ByteString node.getAddress().toString(), ASCII
-    chainCode: new ByteString (Convert.toHexByte(n) for n in node.chainCode).join(''), HEX
-    publicKey: new ByteString do (->
-      node.pubKey.compressed = false
-      node.pubKey.toHex()
-    )
-    , HEX
-
-class ledger.wallet.WalletSetupConsistencyChecker
-
-  constructor: () ->
-
-  isWalletSetupConsistent: (wallet, callback) ->
-    store().get "__wscc_", (items) =>
-      {__wscc_} = items
-      return callback?(true) unless __wscc_?
-      @_createConsistencyTest wallet, (test) =>
-        isConsistent = _.isEqual(__wscc_, test)
-        store().remove(["__wscc_"]) if isConsistent
-        callback?(isConsistent)
-
-  setConsistencyTest: (walletSeed) ->
-    wallet = new MemoryWallet(walletSeed)
-    @_createConsistencyTest wallet, (test) =>
-      store().set __wscc_: test
-
-  _createConsistencyTest: (wallet, callback) ->
-    xpub = new ledger.wallet.ExtendedPublicKey(wallet, "44'/0'/0'", no)
-    xpub.initialize ->
-      wallet.getPublicAddress "0'/0/0xb11e", (bitIdAddress) ->
-        wallet.getPublicAddress "0x50DA'/0xBED'/0xC0FFEE'", (storageAddress) ->
-          callback(xpub: xpub.toString(), bitid: bitIdAddress.bitcoinAddress.toString(ASCII), storage: storageAddress.bitcoinAddress.toString(ASCII))


Kloud: add metrics to datadog for kloud and klient info calls

--- client/Main/kite/kites/kloud.coffee
+++ client/Main/kite/kites/kloud.coffee
@@ -71,6 +71,8 @@
     if not klientKite?
       return callback null

+    KD.remote.api.DataDog.increment "KlientInfo", noop
+
     klientKite.ping()

       .then (res)->
@@ -93,6 +95,8 @@

     info "[kloud:info] call count for [#{machineId}] is #{@kloudCalls[machineId]}"

+    KD.remote.api.DataDog.increment "KloudInfo", noop
+
     @tell 'info', { machineId }

       .then (info) =>


Simplified parsing of error code

Simplified parsing of error code

--- client/app/lib/util/s3upload.coffee
+++ client/app/lib/util/s3upload.coffee
@@ -47,8 +47,8 @@
       data        : data
       timeout     : timeout
       error       : (xhr) ->
-        responseText = $.parseXML xhr.responseText
-        errorCode    = responseText.getElementsByTagName("Code")[0].innerHTML
+        responseText = xhr.responseText
+        errorCode    = $(responseText).find("Code").text()
         if errorCode is 'EntityTooLarge'
             callback message: 'The file you tried to upload is too big'
         else


Sidebar: Select workspace item on the sidebar when IDE workspace changed.

--- client/landing/Core/mainview.coffee
+++ client/landing/Core/mainview.coffee
@@ -9,6 +9,9 @@
     super options, data

     @notifications = []
+
+    @on 'WorkspaceSelected', (options) =>
+      @activitySidebar.selectWorkspace options

   viewAppended: ->


some vertical space between methods

some vertical space between methods

--- client/plugins/method/method.coffee
+++ client/plugins/method/method.coffee
@@ -158,7 +158,7 @@

       dispatch list, allocated, lines, report, (report) ->
         text = report.join "\n"
-        table = $('
').html text
+        table = $('
').html text
         div.append table
         div.dblclick -> wiki.textEditor div, item


Set default write concern to avoid warnings

--- acs.coffee
+++ acs.coffee
@@ -15,11 +15,13 @@
 dbserver = new mongo.Server(config.MONGODB_SOCKET, 0, {auto_reconnect: true})
 tasksCollection = null
 devicesCollection = null
-db = new mongo.Db(config.DATABASE_NAME, dbserver, {native_parser:true})
+db = new mongo.Db(config.DATABASE_NAME, dbserver, {native_parser:true, safe:true})
+
 db.open( (err, db) ->
   db.collection('tasks', (err, collection) ->
     tasksCollection = collection
-    tasksCollection.ensureIndex({device: 1, timestamp: 1})
+    tasksCollection.ensureIndex({device: 1, timestamp: 1}, (err) ->
+    )
   )

   db.collection('devices', (err, collection) ->


Update the keyup specs to work with keyups as multi-stroke bindings

--- spec/keymap-manager-spec.coffee
+++ spec/keymap-manager-spec.coffee
@@ -348,38 +348,43 @@

         events = []
         elementA.addEventListener 'y-command', (e) -> events.push('y-keydown')
-        elementA.addEventListener 'y-command-up', (e) -> events.push('y-keyup')
+        elementA.addEventListener 'y-command-ctrl-up', (e) -> events.push('y-ctrl-keyup')
+        elementA.addEventListener 'x-command-ctrl-up', (e) -> events.push('x-ctrl-keyup')
+        elementA.addEventListener 'y-command-y-up-ctrl-up', (e) -> events.push('y-up-ctrl-keyup')

         keymapManager.add "test",
           ".a":
             "ctrl-y": "y-command"
-            "ctrl-y^ctrl": "y-command-up"
-
-      it "dispatches the y-keyup command when a matching keystroke precedes it", ->
+            "ctrl-y ^ctrl": "y-command-ctrl-up"
+            "ctrl-x ^ctrl": "x-command-ctrl-up"
+            "ctrl-y ^ctrl-y ^ctrl": "y-command-y-up-ctrl-up"
+
+      it "dispatches the command when a matching keystroke precedes it", ->
         keymapManager.handleKeyboardEvent(buildKeydownEvent('y', ctrl: true, target: elementA))
+        keymapManager.handleKeyboardEvent(buildKeyupEvent('y', ctrl: true, target: elementA))
+        keymapManager.handleKeyboardEvent(buildKeyupEvent('ctrl', target: elementA))
+        advanceClock(keymapManager.getPartialMatchTimeout())
+        expect(events).toEqual ['y-up-ctrl-keyup']
+
+      it "dispatches a command when the keybinding does not specify all key up events", ->
+        keymapManager.handleKeyboardEvent(buildKeydownEvent('x', ctrl: true, target: elementA))
+        keymapManager.handleKeyboardEvent(buildKeyupEvent('x', ctrl: true, target: elementA))
+        keymapManager.handleKeyboardEvent(buildKeyupEvent('ctrl', target: elementA))
+        advanceClock(keymapManager.getPartialMatchTimeout())
+        expect(events).toEqual ['x-ctrl-keyup']
+
+      it "dispatches the y-up-ctrl-keyup command when a matching keystroke precedes it", ->
+        keymapManager.handleKeyboardEvent(buildKeydownEvent('y', ctrl: true, target: elementA))
+        keymapManager.handleKeyboardEvent(buildKeyupEvent('ctrl', target: elementA))
+        advanceClock(keymapManager.getPartialMatchTimeout())
+        expect(events).toEqual ['y-ctrl-keyup']
+
+      it "doesn't dispatch the y-up-ctrl-keyup command when a non-matching keystroke precedes it", ->
+        keymapManager.handleKeyboardEvent(buildKeydownEvent('y', ctrl: true, target: elementA))
+        keymapManager.handleKeyboardEvent(buildKeydownEvent('z', ctrl: true, target: elementA))
+        keymapManager.handleKeyboardEvent(buildKeyupEvent('ctrl', target: elementA))
+        advanceClock(keymapManager.getPartialMatchTimeout())
         expect(events).toEqual ['y-keydown']
-
-        keymapManager.handleKeyboardEvent(buildKeydownEvent('y', ctrl: true, target: elementA))
-        expect(events).toEqual ['y-keydown', 'y-keydown']
-
-        keymapManager.handleKeyboardEvent(buildKeyupEvent('y', ctrl: true, target: elementA))
-        expect(events).toEqual ['y-keydown', 'y-keydown']
-
-        keymapManager.handleKeyboardEvent(buildKeyupEvent('ctrl', target: elementA))
-        expect(events).toEqual ['y-keydown', 'y-keydown', 'y-keyup']
-
-      it "doesn't dispatch the y-keyup command when a non-matching keystroke precedes it", ->
-        keymapManager.handleKeyboardEvent(buildKeydownEvent('y', ctrl: true, target: elementA))
-        expect(events).toEqual ['y-keydown']
-
-        keymapManager.handleKeyboardEvent(buildKeydownEvent('y', ctrl: true, target: elementA))
-        expect(events).toEqual ['y-keydown', 'y-keydown']
-
-        keymapManager.handleKeyboardEvent(buildKeydownEvent('x', ctrl: true, target: elementA))
-        expect(events).toEqual ['y-keydown', 'y-keydown']
-
-        keymapManager.handleKeyboardEvent(buildKeyupEvent('ctrl', target: elementA))
-        expect(events).toEqual ['y-keydown', 'y-keydown']

     it "only counts entire keystrokes when checking for partial matches", ->
       element = $$ -> @div class: 'a'


Решение проблемы с инвалидацией csrf-токена

--- app/assets/javascripts/langtrainer_frontend_backbone/models/extensions/validation.js.coffee
+++ app/assets/javascripts/langtrainer_frontend_backbone/models/extensions/validation.js.coffee
@@ -6,12 +6,12 @@
         $('meta[name="csrf-token"]').attr('content', token)

         if model.get('retryWithNewCSRFToken')
-        @clearValidationErrors()
-        @pushValidationError(@validationError, 'serverError', 'При выполнении операции возникла непредвиденная ошибка')
-        @trigger 'error:unprocessable'
+          @clearValidationErrors()
+          @pushValidationError(@validationError, 'serverError', 'При выполнении операции возникла непредвиденная ошибка')
+          @trigger 'error:unprocessable'
         else
-        model.set('retryWithNewCSRFToken', true)
-        model.save()
+          model.set('retryWithNewCSRFToken', true)
+          model.save()
       else
         @validationError = xhr.responseJSON.errors
         @trigger 'error:unprocessable'


Fix admin view when not an admin

Fix admin view when not an admin

--- app/views/admin/MainAdminView.coffee
+++ app/views/admin/MainAdminView.coffee
@@ -31,14 +31,15 @@
   getTitle: -> return $.i18n.t('account_settings.admin')

   initialize: ->
+    @campaigns = new Campaigns()
+    @courses = new CocoCollection([], { url: "/db/course", model: Course})
+
     if window.amActually
       @amActually = new User({_id: window.amActually})
       @amActually.fetch()
       @supermodel.trackModel(@amActually)
     if me.isAdmin()
-      @campaigns = new Campaigns()
       @supermodel.trackRequest @campaigns.fetchByType('course', { data: { project: 'levels' } })
-      @courses = new CocoCollection([], { url: "/db/course", model: Course})
       @supermodel.loadCollection(@courses, 'courses')
     super()


discount is always non-negative

discount is always non-negative

--- resources/assets/coffee/_classes/store-supporter-tag.coffee
+++ resources/assets/coffee/_classes/store-supporter-tag.coffee
@@ -40,7 +40,7 @@
       (@price / @duration).toFixed(2)
     discount: ->
       raw = if @duration >= 12 then 46 else ((1 - (@price / @duration) / 4) * 100)
-      Math.round(raw, 0)
+      Math.max(0, Math.round(raw, 0))
   }

   constructor: ->


remove excessive listeners

remove excessive listeners

--- resources/assets/coffee/_classes/store-supporter-tag.coffee
+++ resources/assets/coffee/_classes/store-supporter-tag.coffee
@@ -43,6 +43,7 @@

     $(document).on 'turbolinks:before-cache.store-supporter-tag', =>
       @reactElement.dataset.user = JSON.stringify(@user)
+      $(document).off '.store-supporter-tag'

     @cost = @calculate(@initializeSlider().slider('value'))
     @initializeSliderPresets()


removing console.log

removing console.log

--- shared/idle/controllers/maps/IdleMapViewerController.coffee
+++ shared/idle/controllers/maps/IdleMapViewerController.coffee
@@ -175,7 +175,6 @@
         $http.post "//api.idle.land/game/map", {map: mapName}
         .then (res) ->
           $scope.currentMap = res.data.map
-          console.log $scope.currentMap
           _.each $scope.currentMap?.map?.layers[2].objects, (object) ->

             object.properties =


Source also updated

Source also updated

--- source/spec/lib/cli/io/readFile.coffee
+++ source/spec/lib/cli/io/readFile.coffee
@@ -5,7 +5,7 @@
     it: 'reads a file with a given name'
     i: [ 'spec/examples/empty_object' ]
     e: """
-// Generated by CoffeeScript 1.12.3
+// Generated by CoffeeScript 1.12.6
 (function() {
   'use strict';
   module.exports = {};


fixed broken getEtherpadAPI calls...

fixed broken getEtherpadAPI calls...

--- api.coffee
+++ api.coffee
@@ -161,7 +161,7 @@
       return
     validateApiKey req, res, (user) ->
       try
-        data = getEtherpadAPI "getHTML", "ctf#{req.params.ctf}", (data, err) ->
+        data = getEtherpadAPI "getHTML", "ctf#{req.params.ctf}", {},  (data, err) ->
           if not err
             res.json {html: data.data.html}
           else
@@ -177,7 +177,7 @@
       return
     validateApiKey req, res, (user) ->
       try
-        data = getEtherpadAPI "getText", "ctf#{req.params.ctf}", (data, err) ->
+        data = getEtherpadAPI "getText", "ctf#{req.params.ctf}", {}, (data, err) ->
           if not err
             res.json {text: data.data.text}
           else
@@ -277,7 +277,7 @@
       return
     validateApiKey req, res, (user) ->
       try
-        data = getEtherpadAPI "getHTML", "challenge#{req.params.challenge}", (data, err) ->
+        data = getEtherpadAPI "getHTML", "challenge#{req.params.challenge}", {}, (data, err) ->
           if not err
             res.json {html: data.data.html}
           else
@@ -293,7 +293,7 @@
       return
     validateApiKey req, res, (user) ->
       try
-        data = getEtherpadAPI "getText", "challenge#{req.params.challenge}", (data, err) ->
+        data = getEtherpadAPI "getText", "challenge#{req.params.challenge}", {}, (data, err) ->
           if not err
             res.json {text: data.data.text}
           else


Fix room presence.

Fix room presence.

--- app.coffee
+++ app.coffee
@@ -100,6 +100,10 @@

   res.redirect '/auth/success'

+app.get '/logout', (req, res) ->
+  req.session.destroy()
+  res.redirect '/'
+
 app.get '/auth/success', (req, res) ->
   if req.session.redirectTo
     res.redirect req.session.redirectTo
@@ -164,19 +168,22 @@
   r.on 'message', messageHandler

   msg = {event: req.params.res, payload: {type: 'connected', src: req.user}}
-  if req.params.res.match(/profile/)
-    profile_id = req.params.res.split('-')[1]
-    redis.publish "user:#{profile_id}", JSON.stringify(msg)
-  else
-    redis.publish "channel:#{req.user.id}", JSON.stringify(msg)
-
-  # FIXME
-  # if req.params.res == 'room' then r.subscribe "room:#{req.params.id}"
+
+  [x, rid] = req.params.res.split('-')
+
+  switch x
+    when 'profile'
+      redis.publish "user:#{rid}", JSON.stringify(msg)
+    when 'room'
+      redis.publish "room:#{rid}", JSON.stringify(msg)
+      r.subscribe "room:#{rid}"
+    when 'home'
+      redis.publish "channel:#{req.user.id}", JSON.stringify(msg)

   # Clear heartbeat and listener
   req.on 'close', =>
     clearInterval hbt
-    r.unsubscribe "room:#{req.params.id}"
+    r.unsubscribe "room:#{rid}"
     r.removeListener 'message', messageHandler

 # Backbone path


removing partial we don't need

removing partial we don't need

--- app.coffee
+++ app.coffee
@@ -24,7 +24,6 @@
 app.set 'partials',
   header: 'partials/header'
   footer: 'partials/footer'
-  footer_scripts: 'partials/footer-scripts'

 # app.enable 'view cache'
 app.engine 'html', require('hogan-express')


add dynamic routes back

add dynamic routes back

--- app.coffee
+++ app.coffee
@@ -32,7 +32,7 @@
     app.use express.bodyParser uploadDir: config.WebService.uploadsDir

     # non-static routes
-    #new Routes(app)
+    new Routes(app)

 #configurations specific to this config environment


[modularization] Use stylus

[modularization] Use stylus

--- app.coffee
+++ app.coffee
@@ -15,6 +15,9 @@
 app.use express.urlencoded()
 app.use express.methodOverride()
 app.use app.router
+app.use '/static', require('stylus').middleware(
+  src: path.join __dirname, 'public'
+)
 app.use '/static', express.static(path.join(__dirname, 'public'))

 if 'development' == app.get 'env'


fix restriction

fix restriction

--- app.coffee
+++ app.coffee
@@ -249,7 +249,7 @@
 	req.logout()
 	res.redirect(if req.cookies.mobile then '/m' else '/')

-app.get '/teamHuman', authorize(), (req, res) ->
+app.get '/teamHuman', authorize('human'), (req, res) ->
 	tmpl_dir = __dirname + '/views/news/forHuman/'
 	# read all news in the directory
 	fs.readdir tmpl_dir, (err, news) ->


Start porting `TextEditorComponent` specs

--- spec/text-editor-component-spec.coffee
+++ spec/text-editor-component-spec.coffee
@@ -89,19 +89,19 @@
       expect(tilesNodes.length).toBe(3)

       expect(tilesNodes[0].style['-webkit-transform']).toBe "translate3d(0px, 0px, 0px)"
-      expect(tilesNodes[0].children.length).toBe(tileSize)
+      expect(tilesNodes[0].querySelectorAll(".line").length).toBe(tileSize)
       expectTileContainsRow(tilesNodes[0], 0, top: 0 * lineHeightInPixels)
       expectTileContainsRow(tilesNodes[0], 1, top: 1 * lineHeightInPixels)
       expectTileContainsRow(tilesNodes[0], 2, top: 2 * lineHeightInPixels)

       expect(tilesNodes[1].style['-webkit-transform']).toBe "translate3d(0px, #{1 * tileHeight}px, 0px)"
-      expect(tilesNodes[1].children.length).toBe(tileSize)
+      expect(tilesNodes[1].querySelectorAll(".line").length).toBe(tileSize)
       expectTileContainsRow(tilesNodes[1], 3, top: 0 * lineHeightInPixels)
       expectTileContainsRow(tilesNodes[1], 4, top: 1 * lineHeightInPixels)
       expectTileContainsRow(tilesNodes[1], 5, top: 2 * lineHeightInPixels)

       expect(tilesNodes[2].style['-webkit-transform']).toBe "translate3d(0px, #{2 * tileHeight}px, 0px)"
-      expect(tilesNodes[2].children.length).toBe(tileSize)
+      expect(tilesNodes[2].querySelectorAll(".line").length).toBe(tileSize)
       expectTileContainsRow(tilesNodes[2], 6, top: 0 * lineHeightInPixels)
       expectTileContainsRow(tilesNodes[2], 7, top: 1 * lineHeightInPixels)
       expectTileContainsRow(tilesNodes[2], 8, top: 2 * lineHeightInPixels)
@@ -118,19 +118,19 @@
       expect(tilesNodes.length).toBe(3)

       expect(tilesNodes[0].style['-webkit-transform']).toBe "translate3d(0px, -5px, 0px)"
-      expect(tilesNodes[0].children.length).toBe(tileSize)
+      expect(tilesNodes[0].querySelectorAll(".line").length).toBe(tileSize)
       expectTileContainsRow(tilesNodes[0], 3, top: 0 * lineHeightInPixels)
       expectTileContainsRow(tilesNodes[0], 4, top: 1 * lineHeightInPixels)
       expectTileContainsRow(tilesNodes[0], 5, top: 2 * lineHeightInPixels)

       expect(tilesNodes[1].style['-webkit-transform']).toBe "translate3d(0px, #{1 * tileHeight - 5}px, 0px)"
-      expect(tilesNodes[1].children.length).toBe(tileSize)
+      expect(tilesNodes[1].querySelectorAll(".line").length).toBe(tileSize)
       expectTileContainsRow(tilesNodes[1], 6, top: 0 * lineHeightInPixels)
       expectTileContainsRow(tilesNodes[1], 7, top: 1 * lineHeightInPixels)
       expectTileContainsRow(tilesNodes[1], 8, top: 2 * lineHeightInPixels)

       expect(tilesNodes[2].style['-webkit-transform']).toBe "translate3d(0px, #{2 * tileHeight - 5}px, 0px)"
-      expect(tilesNodes[2].children.length).toBe(tileSize)
+      expect(tilesNodes[2].querySelectorAll(".line").length).toBe(tileSize)
       expectTileContainsRow(tilesNodes[2], 9, top: 0 * lineHeightInPixels)
       expectTileContainsRow(tilesNodes[2], 10, top: 1 * lineHeightInPixels)
       expectTileContainsRow(tilesNodes[2], 11, top: 2 * lineHeightInPixels)
@@ -1219,7 +1219,7 @@

       expect(regions.length).toBe 1
       regionRect = regions[0].style
-      expect(regionRect.top).toBe (9 * lineHeightInPixels - verticalScrollbarNode.scrollTop) + 'px'
+      expect(regionRect.top).toBe (0 + 'px')
       expect(regionRect.height).toBe 1 * lineHeightInPixels + 'px'
       expect(regionRect.left).toBe 2 * charWidth + 'px'
       expect(regionRect.width).toBe 2 * charWidth + 'px'
@@ -1263,10 +1263,10 @@
     it "allows multiple space-delimited decoration classes", ->
       decoration.setProperties(type: 'highlight', class: 'foo bar')
       nextAnimationFrame()
-      expect(componentNode.querySelectorAll('.foo.bar').length).toBe 1
+      expect(componentNode.querySelectorAll('.foo.bar').length).toBe 2
       decoration.setProperties(type: 'highlight', class: 'bar baz')
       nextAnimationFrame()
-      expect(componentNode.querySelectorAll('.bar.baz').length).toBe 1
+      expect(componentNode.querySelectorAll('.bar.baz').length).toBe 2

     it "renders classes on the regions directly if 'deprecatedRegionClass' option is defined", ->
       decoration = editor.decorateMarker(marker, type: 'highlight', class: 'test-highlight', deprecatedRegionClass: 'test-highlight-region')
@@ -1278,7 +1278,7 @@
     describe "when flashing a decoration via Decoration::flash()", ->
       highlightNode = null
       beforeEach ->
-        highlightNode = componentNode.querySelector('.test-highlight')
+        highlightNode = componentNode.querySelectorAll('.test-highlight')[1]

       it "adds and removes the flash class specified in ::flash", ->
         expect(highlightNode.classList.contains('flash-class')).toBe false
@@ -1314,13 +1314,15 @@
         regionStyle = componentNode.querySelector('.test-highlight .region').style
         originalTop = parseInt(regionStyle.top)

+        expect(originalTop).toBe(2 * lineHeightInPixels)
+
         editor.getBuffer().insert([0, 0], '\n')
         nextAnimationFrame()

         regionStyle = componentNode.querySelector('.test-highlight .region').style
         newTop = parseInt(regionStyle.top)

-        expect(newTop).toBe originalTop + lineHeightInPixels
+        expect(newTop).toBe(0)

       it "moves rendered highlights when the marker is manually moved", ->
         regionStyle = componentNode.querySelector('.test-highlight .region').style
@@ -1330,7 +1332,7 @@
         nextAnimationFrame()

         regionStyle = componentNode.querySelector('.test-highlight .region').style
-        expect(parseInt(regionStyle.top)).toBe 5 * lineHeightInPixels
+        expect(parseInt(regionStyle.top)).toBe 2 * lineHeightInPixels

     describe "when a decoration is updated via Decoration::update", ->
       it "renders the decoration's new params", ->


:green_heart:

--- spec/text-editor-component-spec.coffee
+++ spec/text-editor-component-spec.coffee
@@ -972,21 +972,45 @@
       expect(region2Rect.left).toBe scrollViewClientLeft + 0
       expect(region2Rect.width).toBe 10 * charWidth

-    it "renders 3 regions for selections with more than 2 lines", ->
-      editor.setSelectedScreenRange([[1, 6], [5, 10]])
-      nextAnimationFrame()
-      regions = componentNode.querySelectorAll('.selection .region')
-      expect(regions.length).toBe 3
+    it "renders 3 regions per tile for selections with more than 2 lines", ->
+      editor.setSelectedScreenRange([[0, 6], [5, 10]])
+      nextAnimationFrame()
+
+      # Tile 0
+      regions = componentNode.querySelectorAll(".tile")[0].querySelectorAll('.selection .region')
+      expect(regions.length).toBe(3)

       region1Rect = regions[0].getBoundingClientRect()
-      expect(region1Rect.top).toBe 1 * lineHeightInPixels
+      expect(region1Rect.top).toBe 0
       expect(region1Rect.height).toBe 1 * lineHeightInPixels
       expect(region1Rect.left).toBe scrollViewClientLeft + 6 * charWidth
       expect(region1Rect.right).toBe scrollViewNode.getBoundingClientRect().right

       region2Rect = regions[1].getBoundingClientRect()
-      expect(region2Rect.top).toBe 2 * lineHeightInPixels
-      expect(region2Rect.height).toBe 3 * lineHeightInPixels
+      expect(region2Rect.top).toBe 1 * lineHeightInPixels
+      expect(region2Rect.height).toBe 1 * lineHeightInPixels
+      expect(region2Rect.left).toBe scrollViewClientLeft + 0
+      expect(region2Rect.right).toBe scrollViewNode.getBoundingClientRect().right
+
+      region3Rect = regions[2].getBoundingClientRect()
+      expect(region3Rect.top).toBe 2 * lineHeightInPixels
+      expect(region3Rect.height).toBe 1 * lineHeightInPixels
+      expect(region3Rect.left).toBe scrollViewClientLeft + 0
+      expect(region3Rect.right).toBe scrollViewNode.getBoundingClientRect().right
+
+      # Tile 3
+      regions = componentNode.querySelectorAll(".tile")[1].querySelectorAll('.selection .region')
+      expect(regions.length).toBe(3)
+
+      region1Rect = regions[0].getBoundingClientRect()
+      expect(region1Rect.top).toBe 3 * lineHeightInPixels
+      expect(region1Rect.height).toBe 1 * lineHeightInPixels
+      expect(region1Rect.left).toBe scrollViewClientLeft + 0
+      expect(region1Rect.right).toBe scrollViewNode.getBoundingClientRect().right
+
+      region2Rect = regions[1].getBoundingClientRect()
+      expect(region2Rect.top).toBe 4 * lineHeightInPixels
+      expect(region2Rect.height).toBe 1 * lineHeightInPixels
       expect(region2Rect.left).toBe scrollViewClientLeft + 0
       expect(region2Rect.right).toBe scrollViewNode.getBoundingClientRect().right


Make randomized presenter spec failures easier to reproduce

--- spec/text-editor-presenter-spec.coffee
+++ spec/text-editor-presenter-spec.coffee
@@ -1763,54 +1763,68 @@
         expect(presenter.state.height).toBe editor.getScreenLineCount() * 20

   describe "when the model and view measurements are mutated randomly", ->
-    [editor, buffer, presenter, presenterParams] = []
+    [editor, buffer, presenterParams, presenter, statements] = []

     it "correctly maintains the presenter state", ->
-      _.times 10, ->
+      _.times 20, ->
         waits(0)
-
         runs ->
-          buffer = new TextBuffer
-          editor = new TextEditor({buffer})
-          editor.setEditorWidthInChars(80)
-
-          presenterParams =
-            model: editor
-            height: 50
-            contentFrameWidth: 300
-            scrollTop: 0
-            scrollLeft: 0
-            lineHeight: 10
-            baseCharacterWidth: 10
-            lineOverdrawMargin: 1
-
-          presenter = new TextEditorPresenter(presenterParams)
-          referencePresenter = new TextEditorPresenter(presenterParams)
-          expect(presenter.state).toEqual referencePresenter.state
-
-          actions = []
-          _.times 30, ->
-            performRandomAction (action) -> actions.push(action)
-            actualState = presenter.state
-            expectedState = new TextEditorPresenter(presenterParams).state
-            delete actualState.content.scrollingVertically
-            delete expectedState.content.scrollingVertically
-
-            unless _.isEqual(actualState, expectedState)
-              console.log "Prestenter states differ >>>>>>>>>>>>>>>>"
-              console.log "Actual:", actualState
-              console.log "Expected:", expectedState
-              console.log "Uncomment code below this line to see a JSON diff"
-              # {diff} = require 'json-diff' # !!! Run `npm install json-diff` in your `atom/` repository
-              # console.log "Difference:", diff(actualState, expectedState)
-              console.log ""
-              console.log "Actions:"
-              console.log action for action in actions
-              console.log ""
-              console.log "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"
-              throw new Error("Unexpected presenter state after random mutation. Check console output for details.")
-
-          buffer.destroy()
+          performSetup()
+          _.times 20, ->
+            performRandomAction (statement) -> statements.push(statement)
+            expectValidState()
+          performTeardown()
+
+    xit "works correctly for a particular stream of random actions", ->
+      performSetup()
+      # paste output from failing spec here
+      expectValidState()
+      performTeardown()
+
+    performSetup = ->
+      buffer = new TextBuffer
+      editor = new TextEditor({buffer})
+      editor.setEditorWidthInChars(80)
+      presenterParams =
+        model: editor
+        height: 50
+        contentFrameWidth: 300
+        scrollTop: 0
+        scrollLeft: 0
+        lineHeight: 10
+        baseCharacterWidth: 10
+        lineOverdrawMargin: 1
+        horizontalScrollbarHeight: 5
+        verticalScrollbarWidth: 5
+      presenter = new TextEditorPresenter(presenterParams)
+      statements = []
+
+    performTeardown = ->
+      buffer.destroy()
+
+    expectValidState = ->
+      presenterParams.scrollTop = presenter.scrollTop
+      actualState = presenter.state
+      expectedState = new TextEditorPresenter(presenterParams).state
+      delete actualState.content.scrollingVertically
+      delete expectedState.content.scrollingVertically
+
+      unless _.isEqual(actualState, expectedState)
+        console.log "Prestenter states differ >>>>>>>>>>>>>>>>"
+        console.log "Actual:", actualState
+        console.log "Expected:", expectedState
+        console.log "Uncomment code below this line to see a JSON diff"
+        # {diff} = require 'json-diff' # !!! Run `npm install json-diff` in your `atom/` repository
+        # console.log "Difference:", diff(actualState, expectedState)
+        if statements.length > 0
+          console.log """
+            =====================================================
+            Paste this code into the disabled spec in this file (and enable it) to repeat this failure:
+
+            #{statements.join('\n')}
+            =====================================================
+          """
+        throw new Error("Unexpected presenter state after random mutation. Check console output for details.")

     performRandomAction = (log) ->
       getRandomElement([
@@ -1826,11 +1840,7 @@
     changeScrollTop = (log) ->
       scrollHeight = presenterParams.lineHeight * editor.getScreenLineCount()
       newScrollTop = Math.max(0, _.random(0, scrollHeight - presenterParams.height))
-      log """
-        presenterParams.scrollTop = #{newScrollTop}
-        presenter.setScrollTop(#{newScrollTop})
-      """
-      presenterParams.scrollTop = newScrollTop
+      log "presenter.setScrollTop(#{newScrollTop})"
       presenter.setScrollTop(newScrollTop)

     changeScrollLeft = (log) ->


activity/router: minor

activity/router: minor

--- client/activity/lib/routehandlers.coffee
+++ client/activity/lib/routehandlers.coffee
@@ -114,3 +114,5 @@
   callback    ?= (app) -> app.getView().open type, slug
   {appManager, mainController} = kd.singletons
   mainController.ready -> appManager.open 'Activity', callback
+
+


welcome: added as an app with sidebar

welcome: added as an app with sidebar

--- client/app/lib/mainviewcontroller.coffee
+++ client/app/lib/mainviewcontroller.coffee
@@ -87,10 +87,10 @@
     html     = global.document.documentElement
     mainView = @getView()

-    fullSizeApps = ['Login', 'Pricing', 'Activity', 'Teams']
+    fullSizeApps    = [ 'Login', 'Pricing', 'Activity', 'Teams' ]
     appsWithSidebar = [
       'Activity', 'Members', 'content-display', 'Apps', 'Dashboard', 'Account'
-      'Environments', 'Bugs'
+      'Environments', 'Bugs', 'Welcome'
     ]

     if (isApp = behavior is 'application') or (name in fullSizeApps)


videoLinkShell: bugfix input.end event handling

--- coffee/src/shells/videolink.shell.coffee
+++ coffee/src/shells/videolink.shell.coffee
@@ -117,7 +117,7 @@
     'change input.start': => @timeInputChanged 'start'
     'blur input.start':  => @timeInputChanged 'start'
     'change input.end': => @timeInputChanged 'end'
-    'blur input.end': @onClickLoopsButton
+    'blur input.end': => @timeInputChanged 'end'
     'click button.loops': @onClickLoopsButton
     'change input.n-loops': @onChangeNLoops
     'blur input.n-loops': @onChangeNLoops


Update ship tag to 2015 autumn event

Update ship tag to 2015 autumn event

--- views/components/main/parts/miniship-parts/statuslabel.cjsx
+++ views/components/main/parts/miniship-parts/statuslabel.cjsx
@@ -15,19 +15,19 @@
         <FontAwesome key={0} name='wrench' />
       </OverlayTrigger>
     else if @props.label? and @props.label == 2
-      <OverlayTrigger placement="top" overlay={<Tooltip id="statuslabel-status-2">{__ 'Ship tag: %s', 'E1, E2, E7'}</Tooltip>}>
+      <OverlayTrigger placement="top" overlay={<Tooltip id="statuslabel-status-2">{__ 'Ship tag: %s', 'E1, E2, E3'}</Tooltip>}>
         <FontAwesome key={0} name='tag' />
       </OverlayTrigger>
     else if @props.label? and @props.label == 3
-      <OverlayTrigger placement="top" overlay={<Tooltip id="statuslabel-status-3">{__ 'Ship tag: %s', 'E3, E6, E7'}</Tooltip>}>
+      <OverlayTrigger placement="top" overlay={<Tooltip id="statuslabel-status-3">{__ 'Ship tag: %s', 'E4'}</Tooltip>}>
         <FontAwesome key={0} name='tag' />
       </OverlayTrigger>
     else if @props.label? and @props.label == 4
-      <OverlayTrigger placement="top" overlay={<Tooltip id="statuslabel-status-4">{__ 'Ship tag: %s', 'E4, E7'}</Tooltip>}>
+      <OverlayTrigger placement="top" overlay={<Tooltip id="statuslabel-status-4">{__ 'Ship tag: %s', '?'}</Tooltip>}>
         <FontAwesome key={0} name='tag' />
       </OverlayTrigger>
     else if @props.label? and @props.label == 5
-      <OverlayTrigger placement="top" overlay={<Tooltip id="statuslabel-status-5">{__ 'Ship tag: %s', 'E5'}</Tooltip>}>
+      <OverlayTrigger placement="top" overlay={<Tooltip id="statuslabel-status-5">{__ 'Ship tag: %s', '?'}</Tooltip>}>
         <FontAwesome key={0} name='tag' />
       </OverlayTrigger>
     else if @props.label? and @props.label == 6