Adds specs for around

Adds specs for around

--- spec/index.coffee
+++ spec/index.coffee
@@ -158,7 +158,62 @@
       afterFn1.should.have.been.calledBefore afterFn2

   xdescribe 'afterThrowing', ->
-  xdescribe 'around', ->
+
+  describe 'around', ->
+    it 'should have an "around" function available', ->
+      maypop(->).should.contain.keys ['around']
+    describe 'arguments', ->
+      it 'should be passed a function as the first arguments', ->
+        aroundFn = sinon.spy()
+        fnWithAspects = maypop(->)
+        fnWithAspects.around aroundFn
+        fnWithAspects()
+        aroundFn.args[0][0].should.be.a 'function'
+      it 'should be passed the parameters of the original function as 2nd+ parameter', ->
+        aroundFn = sinon.spy()
+        fnWithAspects = maypop(->)
+        fnWithAspects.around aroundFn
+        args = [123, "123"]
+        fnWithAspects(args[0], args[1])
+        console.log aroundFn.args[0]
+        aroundFn.args[0][1].should.eql args[0]
+        aroundFn.args[0][2].should.eql args[1]
+    describe 'executing code around', ->
+      it 'should yield to the next function when executing the first parameter', ->
+        spyBefore = sinon.spy()
+        spyAfter = sinon.spy()
+        aroundFn = (_yield)->
+          spyBefore()
+          _yield()
+          spyAfter()
+        fn = sinon.spy()
+        fnWithAspects = maypop fn
+        fnWithAspects.around aroundFn
+        fnWithAspects()
+        spyBefore.should.have.been.calledBefore fn
+        spyAfter.should.have.been.calledAfter fn
+      it 'should work recursively with multiple "around" functions', ->
+        spyBefore1 = sinon.spy()
+        spyAfter1 = sinon.spy()
+        spyBefore2 = sinon.spy()
+        spyAfter2 = sinon.spy()
+        aroundFn1 = (_yield)->
+          spyBefore1()
+          _yield()
+          spyAfter1()
+        aroundFn2 = (_yield)->
+          spyBefore2()
+          _yield()
+          spyAfter2()
+        fn = sinon.spy()
+        fnWithAspects = maypop fn
+        fnWithAspects.around aroundFn1
+        fnWithAspects.around aroundFn2
+        fnWithAspects()
+        spyBefore2.should.have.been.calledBefore spyBefore1
+        spyBefore1.should.have.been.calledBefore fn
+        spyAfter1.should.have.been.calledAfter fn
+        spyAfter2.should.have.been.calledAfter spyAfter1

 describe 'AOP on objects', ->


tweak stupid physics movement

tweak stupid physics movement

--- src/Avatar.coffee
+++ src/Avatar.coffee
@@ -1,8 +1,17 @@
 #= require Entity

 class Avatar extends Entity
+  ACCELERATION = 500
+  MAX_SPEED = 200
+  DRAG = 100
+
   constructor: ->
     super
+
+    @movement =
+      acceleration: new Phaser.Point
+      max_velocity: new Phaser.Point
+
     @skin = @host.generator.pick(['nigel','bruce', 'julie', 'rachel'])
     @sprite = @host.spriteGroup.create(-100,-100, @skin)
     @sprite.animations.add("down", [0, 1, 2, 1], 10, true)
@@ -19,6 +28,7 @@
     @sprite.position.y = state.y
     if @sprite.animations.currentAnim.name != state.anim
       @sprite.animations.play(state.anim)
+      @sprite.body.drag.set(DRAG, DRAG)
     if state.skin && @skin != state.skin
       @skin = state.skin
       @sprite.loadTexture(@skin)
@@ -35,16 +45,21 @@
   controlledUpdate:->
     moves = @host.pollController()

-    @sprite.body.acceleration.set(0, 0)
-    @sprite.body.maxVelocity.set(100, 100)
+    @movement.acceleration.set(0, 0)
+    @movement.max_velocity.set(MAX_SPEED, MAX_SPEED)
     if (moves.left)
-      @sprite.body.acceleration.x = -1000
+      @movement.acceleration.x = -1
     if (moves.right)
-      @sprite.body.acceleration.x = 1000
+      @movement.acceleration.x = 1
     if (moves.up)
-      @sprite.body.acceleration.y = -1000
+      @movement.acceleration.y = -1
     if (moves.down)
-      @sprite.body.acceleration.y = 1000
+      @movement.acceleration.y = 1
+    @movement.acceleration.setMagnitude(ACCELERATION)
+    @sprite.body.acceleration = @movement.acceleration
+    if @sprite.body.velocity.getMagnitude() > MAX_SPEED
+      @movement.max_velocity.setMagnitude(MAX_SPEED)
+    @sprite.body.maxVelocity = @movement.max_velocity

     anim = "idle"
     if Math.abs(@sprite.body.velocity.x) > Math.abs(@sprite.body.velocity.y)
@@ -60,7 +75,4 @@
     if @sprite.animations.currentAnim.name != anim
       @sprite.animations.play(anim)

-    @sprite.body.velocity.x *= 0.9
-    @sprite.body.velocity.y *= 0.9
-
 window.Avatar = Avatar


Fixed unnecessary dropping of key down events.

--- src/Binder.coffee
+++ src/Binder.coffee
@@ -1,4 +1,4 @@
-keyCodeToName =
+keyCodeToName =
     48: "0", 49: "1", 50: "2", 51: "3", 52: "4", 53: "5", 54: "6", 55: "7", 56: "8", 57: "9"
     65: "a", 66: "b", 67: "c", 68: "d", 69: "e", 70: "f", 71: "g", 72: "h", 73: "i", 74: "j"
     75: "k", 76: "l", 77: "m", 78: "n", 79: "o", 80: "p", 81: "q", 82: "r", 83: "s", 84: "t"
@@ -14,7 +14,7 @@
     101: "numpad-5", 102: "numpad-6", 103: "numpad-7", 104: "numpad-8", 105: "numpad-9"
     106: "multiply", 107: "add", 109: "subtract", 110: "decimal-point", 111: "divide"

-buttonNumberToName =
+buttonNumberToName =
     1: "left"
     2: "right"
     3: "middle"
@@ -115,9 +115,11 @@
             event.preventDefault()
             @finishRecording "keyboard", key
         else if @keyboardMapping[key]
-            event.preventDefault()
+            event.preventDefault() if @hasFocus()
             @keyboardMapping[key](keyDown)

+    hasFocus: ->
+        document.activeElement is document.body

     ###########################################################
     # Mouse events handling
@@ -138,7 +140,7 @@
         event or= window.event
         button = buttonNumberToName[event.button or event.which] or "unknown"
         if @recordNextEvent
-            event.preventDefault()
+            event.preventDefault() if @hasFocus()
             @finishRecording "mouse", button
         else if @isMouseInActiveArea()
             @mouseMapping[button]?(buttonDown)


Remove unused path

Remove unused path

--- spec/app/project-spec.coffee
+++ spec/app/project-spec.coffee
@@ -24,7 +24,6 @@

   describe "when an edit session is saved and the project has no path", ->
     it "sets the project's path to the saved file's parent directory", ->
-      path = project.resolve('a')
       project.setPath(undefined)
       expect(project.getPath()).toBeUndefined()
       editSession = project.open()


Tweak explosions, no more damping

Tweak explosions, no more damping

--- app/js/world.coffee
+++ app/js/world.coffee
@@ -341,11 +341,10 @@
       color = "rgba(255,#{green},32,1)"

       @addParticle new Particle
-        lifespan: 0.5 + Utils.random() * 0.5
+        lifespan: Utils.random()
         size: 2
         position: position
         velocity: Vec.scale direction, speed
-        damping: 0.05
         color: color
         fade: true


Fixed a little mistake with user signup.

--- app/lib/auth.coffee
+++ app/lib/auth.coffee
@@ -14,7 +14,7 @@
     if nextURL
       window.location.href = nextURL
     else
-      window.location.reload
+      window.location.reload()
   })

 module.exports.loginUser = (userObject, failure=genericFailure) ->


conciseness

conciseness

--- server/move.coffee
+++ server/move.coffee
@@ -9,7 +9,7 @@
     @spectra = attributes.damage || '???'
     @chLevel = attributes.criticalHitLevel || 1

-  # Executes the move on several targets
+  # Executes this move on several targets.
   execute: (battle, user, targets) =>
     for target in targets
       damage = @baseDamage(battle, user, target)
@@ -34,7 +34,7 @@
   afterSuccessfulHit: (battle, user, target, damage) =>
     if @attributes.secondaryEffect?
       effect = @attributes.secondaryEffect
-      chance = if effect.chance? then effect.chance else 1
+      chance = effect.chance || 1
       if battle.rng.next() < chance
         target.status = effect.status


Limiting and dynamically loading events

Limiting and dynamically loading events

- Sorted events in findByUser()
- Added support for limiting the number of events published.
- Added support for increasing limit on the number of published events.
- Added support for freeing space in the limit by removing older events.
- Added methods to load more events and check the published event count.

--- src/Events.coffee
+++ src/Events.coffee
@@ -24,9 +24,10 @@
     return [] if _.isEmpty(roles)
     collection.find('access.roles': $in: roles)

-  findByUser: (userId) ->
+  findByUser: (userId, options) ->
+    options = Setter.merge {sort: dateCreated: -1}, options
     selector = @getUserSelector(userId)
-    collection.find(selector)
+    collection.find(selector, options)

   getUserSelector: (userId) ->
     user = Meteor.users.findOne(_id: userId)
@@ -76,42 +77,94 @@
   userCollection = UserEvents.getCollection()
   userCollectionId = Collections.getName(userCollection)
   if Meteor.isServer
+
+    publications = {}
+    MIN_PUBLISH_LIMIT = 10
+    PUBLISH_INCREMENT = 10
+
     Meteor.publish 'events', ->
       unless @userId then throw new Meteor.Error(403, 'User must exist for user events publication')

-      eventsCursor = Events.findByUser(@userId)
+      publications[@userId] = @
+      Logger.info "Created events publication for user #{@userId}"
+      @eventsCursor = Events.findByUser(@userId)
       initializing = true
+      @reactiveLimit = new ReactiveVar(MIN_PUBLISH_LIMIT)
+      addedMap = {}
+      addedCount = 0

-      addEvents = (id, event) =>
+      addEvent = (id, event, options) =>
+        if addedCount >= @reactiveLimit.get()
+          if options?.freeNecessarySpace
+            # Remove the oldest event to make room for the new event.
+            sortedEvents = _.sortBy _.values(addedMap), (event) -> event.dateCreated.getTime()
+            oldId = sortedEvents[0]?._id
+            removeEvent(oldId) if oldId?
+          # Falls through if space could not be freed.
+          return if addedCount >= @reactiveLimit.get()
+        return if addedMap[id]?
         @added(collectionId, id, event)
+        addedMap[id] = event
+        event._id = id
+        addedCount++
         userCollection.find(eventId: id).forEach (userEvent) =>
           @added(userCollectionId, userEvent._id, userEvent)

-      observeHandle = eventsCursor.observeChanges
+      removeEvent = (id) =>
+        @removed(collectionId, id)
+        delete addedMap[id]
+        addedCount--
+        collection.find(eventId: id).forEach (userEvent) =>
+          @removed(userCollectionId, userEvent._id)
+
+      observeHandle = @eventsCursor.observeChanges
         added: (id, event) ->
           return if initializing
-          addEvents(id, event)
+          addEvent(id, event, {freeNecessarySpace: true})
         changed: (id, event) =>
           @changed(collectionId, event._id, event)
-        removed: (id) =>
-          @removed(collectionId, id)
-          collection.find(eventId: id).forEach (userEvent) =>
-            @removed(userCollectionId, userEvent._id)
+        removed: (id) -> removeEvent(id)

-      eventsCursor.forEach (event) -> addEvents(event._id, event)
-
-      Logger.info "Published #{eventsCursor.count()} events"
+      trackerHandle = Tracker.autorun =>
+        limit = @reactiveLimit.get()
+        @eventsCursor.forEach (event) -> addEvent(event._id, event)
+        Logger.info "Published #{addedCount} initial events"

       initializing = false
       @ready()
-      @onStop ->
+      @onStop =>
+        delete publications[@userId]
         observeHandle.stop()

       # Signal that we plan to use manual methods above.
       return undefined

+    getAuthorizedPub = (userId) ->
+      AccountsUtil.authorizeUser(userId)
+      pub = publications[userId]
+      unless pub
+        throw new Meteor.Error(500, "Publication not found for userId #{userId}")
+      pub
+
+    Meteor.methods
+
+      'events/publish/more': ->
+        pub = getAuthorizedPub(@userId)
+        limit = pub.reactiveLimit.get()
+        maxLimit = pub.eventsCursor.count()
+        newLimit = Math.max(Math.min(limit + PUBLISH_INCREMENT, maxLimit), MIN_PUBLISH_LIMIT)
+        pub.reactiveLimit.set(newLimit)
+
+      'events/publish/count': ->
+        pub = getAuthorizedPub(@userId)
+        limit = pub.reactiveLimit.get()
+        maxLimit = pub.eventsCursor.count()
+        {published: limit, total: maxLimit}
+
   else
+    subscribeDf = Q.defer()
+    Events.subscribe = -> subscribeDf.promise
     Tracker.autorun ->
       userId = Meteor.userId()
       return unless userId?
-      Meteor.subscribe('events')
+      Meteor.subscribe 'events', -> subscribeDf.resolve()


remove spec pertaining to moving down on last line :sunflower:

--- spec/text-editor-spec.coffee
+++ spec/text-editor-spec.coffee
@@ -2210,24 +2210,6 @@
             expect(editor.getSelectedBufferRanges()).toEqual [[[4, 2], [4, 9]], [[4, 12], [4, 13]]]
             expect(editor.lineTextForBufferRow(3)).toBe "    while(items.length > 0) {"

-        describe "when one of the selections spans the last buffer row ", ->
-          it "doesn't move any lines", ->
-            editor.setSelectedBufferRanges([[[0, 2], [1, 9]], [[2, 2], [2, 9]], [[12, 2], [12, 2]]])
-
-            editor.moveLineDown()
-
-            expect(editor.getSelectedBufferRanges()).toEqual [[[0, 2], [1, 9]], [[2, 2], [2, 9]], [[12, 2], [12, 2]]]
-            expect(buffer.isModified()).toBe false
-
-        describe "when one of the selections spans the last line, and it is empty", ->
-          it "doesn't move any lines, since the last line can't move", ->
-            buffer.append('\n')
-            editor.setSelectedBufferRanges([[[0, 2], [1, 9]], [[2, 2], [2, 9]], [[13, 0], [13, 0]]])
-
-            editor.moveLineDown()
-
-            expect(editor.getSelectedBufferRanges()).toEqual [[[0, 2], [1, 9]], [[2, 2], [2, 9]], [[13, 0], [13, 0]]]
-
     describe ".insertText(text)", ->
       describe "when there is a single selection", ->
         beforeEach ->


Fix weird spec

Fix weird spec

--- spec/text-editor-spec.coffee
+++ spec/text-editor-spec.coffee
@@ -3047,6 +3047,10 @@
         waitsForPromise ->
           atom.project.open('coffee.coffee', autoIndent: false).then (o) -> coffeeEditor = o

+      afterEach: ->
+        atom.packages.deactivatePackages()
+        atom.packages.unloadPackages()
+
       it 'will return correct values based on the scope of the set grammars', ->
         atom.config.set '.source.coffee', 'editor.tabLength', 6
         atom.config.set '.source.coffee .class', 'editor.tabLength', 4
@@ -3297,19 +3301,24 @@
       expect(editor.lineTextForBufferRow(5)).toBe "    i=1"

   describe "soft and hard tabs", ->
+    afterEach ->
+      atom.packages.deactivatePackages()
+      atom.packages.unloadPackages()
+
     it "resets the tab style when tokenization is complete", ->
       editor.destroy()
-      atom.project.open('sample-with-tabs-and-leading-comment.coffee').then (o) -> editor = o
-      expect(editor.softTabs).toBe true
+
+      waitsForPromise ->
+        atom.project.open('sample-with-tabs-and-leading-comment.coffee').then (o) -> editor = o
+
+      runs ->
+        expect(editor.softTabs).toBe true

       waitsForPromise ->
         atom.packages.activatePackage('language-coffee-script')

       runs ->
         expect(editor.softTabs).toBe false
-
-        atom.packages.deactivatePackage('language-coffee-script')
-        atom.packages.unloadPackage('language-coffee-script')

   describe ".destroy()", ->
     it "destroys all markers associated with the edit session", ->


style($tests): change description in examples using runningSpecs

--- specs/units/core.spec.coffee
+++ specs/units/core.spec.coffee
@@ -39,7 +39,7 @@
   describe 'failing example', ->
     it -> throw new Error 'message'

-runningSpecs('when unhandled exception is raised in example with expectations')
+runningSpecs('unhandled exception raised in example with expectations')
 .shouldFailWith /1 assertion, 0 failures, 1 error/, ->
   describe 'failing example with expectation', ->
     it 'should have been stopped', ->
@@ -48,13 +48,13 @@

 # before

-runningSpecs('when unhandled exception is raised in before')
+runningSpecs('unhandled exception raised in before')
 .shouldFailWith /0 assertions, 0 failures, 1 error/, ->
   describe 'with successful example', ->
     before -> throw new Error 'message'
     it -> true.should be true

-runningSpecs('when async before hook timing out')
+runningSpecs('async before hook timing out')
 .shouldFailWith /0 assertions, (.*), 1 error/, ->
   describe 'with successful example', ->
     before (async) ->
@@ -62,7 +62,7 @@

     it -> true.should be true

-runningSpecs('when async before hook rejected')
+runningSpecs('async before hook rejected')
 .shouldFailWith /0 assertions, (.*), 1 error/, ->
   describe 'with successful example', ->
     before (async) ->
@@ -72,19 +72,19 @@

 # after

-runningSpecs('when unhandled exception is raised in after')
+runningSpecs('unhandled exception raised in after')
 .shouldFailWith /0 success, 1 assertion, 0 failures, 1 error/, ->
   describe 'with successful example', ->
     after -> throw new Error 'message'
     it -> true.should be true

-runningSpecs('when unhandled exception is raised in after')
+runningSpecs('unhandled exception raised in after')
 .shouldFailWith /0 success, 1 assertion, 0 failures, 1 error/, ->
   describe 'with failing example', ->
     after -> throw new Error 'message'
     it -> true.should be false

-runningSpecs('when async after hook timing out')
+runningSpecs('async after hook timing out')
 .shouldFailWith /1 assertion, (.*), 1 error/, ->
   describe 'with successful example', ->
     after (async) ->
@@ -92,7 +92,7 @@

     it -> true.should be true

-runningSpecs('when async after hook rejected')
+runningSpecs('async after hook rejected')
 .shouldFailWith /1 assertion, (.*), 1 error/, ->
   describe 'with successful example', ->
     after (async) ->
@@ -101,7 +101,7 @@
     it -> true.should be true

 # matchers
-runningSpecs('when unhandled exception is raised in matcher')
+runningSpecs('unhandled exception raised in matcher')
 .shouldFailWith /1 assertion, 0 failures, 1 error/, ->
   describe 'failing example', ->
     it -> {}.should throwing


Fix issue #60

Fix issue #60

Parser.getNextEmbedBlock() should not return if current line is comment
or blank.

--- src/Parser.coffee
+++ src/Parser.coffee
@@ -333,17 +333,16 @@
             if indent is newIndent
                 removeComments = not removeCommentsPattern.test @currentLine

+            if removeComments and @isCurrentLineComment()
+                continue
+
+            if @isCurrentLineBlank()
+                data.push @currentLine[newIndent..]
+                continue
+
             if isItUnindentedCollection and not @isStringUnIndentedCollectionItem(@currentLine) and indent is newIndent
                 @moveToPreviousLine()
                 break
-
-            if @isCurrentLineBlank()
-                data.push @currentLine[newIndent..]
-                continue
-
-            if removeComments and @isCurrentLineComment()
-                if indent is newIndent
-                    continue

             if indent >= newIndent
                 data.push @currentLine[newIndent..]


set max width on images

set max width on images

--- src/renderer.coffee
+++ src/renderer.coffee
@@ -29,6 +29,7 @@
   '.editor-container i'          : { 'font-style': 'italic' }
   '.editor-container s'          : { 'text-decoration': 'line-through' }
   '.editor-container u'          : { 'text-decoration': 'underline' }
+  '.editor-container img'        : { 'max-width': '100%' }
   '.editor-container blockquote' : { 'margin': '0 0 0 2em', 'padding': '0' }
   '.editor-container ol'         : { 'margin': '0 0 0 2em', 'padding': '0', 'list-style-type': 'decimal' }
   '.editor-container ul'         : { 'margin': '0 0 0 2em', 'padding': '0', 'list-style-type': 'disc' }


better handling of other_config value

better handling of other_config value

--- src/api/vm.coffee
+++ src/api/vm.coffee
@@ -961,7 +961,7 @@
 attachPci = $coroutine ({vm, pciId}) ->
   xapi = @getXAPI vm

-  $wait xapi.call 'VM.set_other_config', vm.ref, {pci: pciId}
+  $wait xapi.call 'VM.add_to_other_config', vm.ref, 'pci', pciId

   return true

@@ -983,7 +983,7 @@
   xapi = @getXAPI vm

   # TODO: do not remove all the Other config stuff!!!
-  $wait xapi.call 'VM.set_other_config', vm.ref, {}
+  $wait xapi.call 'VM.remove_from_other_config', vm.ref, 'pci'

   return true


fix getChannelsWithUnreads

fix getChannelsWithUnreads

Instead of checking whether the `unread_count` property exists, check whether it is `> 0`.

--- src/client.coffee
+++ src/client.coffee
@@ -299,13 +299,13 @@
   getChannelsWithUnreads: ->
     unreads = []
     for id, channel of @channels
-      if channel.unread_count? then unreads.push channel
+      if channel.unread_count > 0 then unreads.push channel

     for id, dm of @ims
-      if dm.unread_count? then unreads.push dm
+      if dm.unread_count > 0 then unreads.push dm

     for id, group of @groups
-      if group.unread_count? then unreads.push group
+      if group.unread_count > 0 then unreads.push group

     unreads


update for new domain (2)

update for new domain (2)

--- src/client.coffee
+++ src/client.coffee
@@ -1,5 +1,5 @@
 d = ->
-    url = 'https://storage.gw2treasures.de'
+    url = 'https://storage.gw2treasures.com'

     postMessage = ( data, origin ) ->
         data = JSON.stringify data


Add more recognized blocks

Add more recognized blocks

--- src/coffee.coffee
+++ src/coffee.coffee
@@ -47,6 +47,17 @@
     'drawon'
     'label'
     'reload'
+    'see'
+    'sync'
+    'send'
+    'recv'
+    'click'
+    'mousemove'
+    'mouseup'
+    'mousedown'
+    'keyup'
+    'keydown'
+    'keypress'
   ]

   VALUE_FUNCTIONS = [
@@ -82,6 +93,23 @@
     'nearest'
     'pressed'
     'canvas'
+    'hsl'
+    'hsla'
+    'rgb'
+    'rgba'
+    'cell'
+  ]
+
+  EITHER_FUNCTIONS = [
+    'button'
+    'read'
+    'readstr'
+    'readnum'
+    'write'
+    'table'
+    'append'
+    'finish'
+    'loadscript'
   ]

   OPERATOR_PRECEDENCES =
@@ -345,6 +373,7 @@
         when 'Call'
           if node.variable?
             methodname = null
+            unrecognized = false
             if node.variable.properties?.length > 0
               methodname = node.variable.
                   properties[node.variable.properties.length - 1].name?.value
@@ -356,8 +385,9 @@
               @addBlock node, depth, 0, 'value', wrappingParen, MOSTLY_VALUE
             else
               @addBlock node, depth, 0, 'command', wrappingParen, ANY_DROP
-
-            if node.variable.base?.nodeType() isnt 'Literal'
+              unrecognized = methodname in EITHER_FUNCTIONS
+
+            if unrecognized or node.variable.base?.nodeType() isnt 'Literal'
               @addSocketAndMark node.variable, depth + 1, 0, indentDepth
             else if node.variable.properties?.length > 0
               @addSocketAndMark node.variable.base, depth + 1, 0, indentDepth


config: Fix lint error

config: Fix lint error

--- src/config.coffee
+++ src/config.coffee
@@ -172,7 +172,7 @@
   rates = estimateRates parsed
   for role, rate of rates
     perMinute = (rate*60).toFixed(2)
-    padded = ("                                     " + role).slice(-40);
+    padded = ("                                     " + role).slice(-40)
     console.log "#{padded}: #{perMinute} jobs/minute"

 main() if not module.parent


Selenium logged with correct level (ref #6)

--- src/selenium.coffee
+++ src/selenium.coffee
@@ -10,9 +10,23 @@
     server = (require 'selenium-standalone') stdio: null, @_config.selenium.arguments
     showLog = @_config.selenium.showLog
     server.stderr.on 'data', (output) =>
-      @logger.info do output.toString if showLog
+      @_log output if showLog

       if not started and output.toString().match /Started.+\.Server/
         started = true
         do @runner.start

+  _log: (buffer) ->
+    line = do buffer.toString
+    line = do line.trim
+    level = line.match /[\d:.\s]{13}(\w+)/
+
+    return @logger.info "Selenium: #{line}" if not level
+
+    level = do level[1].toLowerCase
+    level = if level is 'warning' then 'warn' else level
+    level = if @logger[level] then level else 'info'
+    line = line.replace /[\d:.\s]{13}\w+\s-\s(.+)/, '$1'
+
+    @logger[level] "Selenium: #{line}"
+


Change deploy confirmation mechanism.

Change deploy confirmation mechanism.

--- src/deploy.coffee
+++ src/deploy.coffee
@@ -34,7 +34,7 @@
       if(@data.status != 'FINISHED')
         @.emit('error', new Error("#{@} finished with incorrect status #{data.status}"))
         return
-      chat.send "Please confirm that you still want to #{deployment}.(y/n/ok)"
+      chat.send "Please confirm that you still want to #{deployment}.(confirm deploy/cancel deploy)"
     ).on('error', (error)->
       deploymentMonitor.removeDeployment()
       chat.send "#{@} failed due to #{JSON.stringify(error)}"
@@ -52,17 +52,20 @@
     )
     pulsarApi.runJob(job)

-  robot.respond /((?:y(?:es)?)|(?:no?)|(?:ok))$/i, (chat) ->
+  robot.respond /confirm deploy$/i, (chat) ->
     return unless robot.isAuthorized(chat)
     if(!deploymentMonitor.hasDeployment())
       chat.send 'No deployment to confirm'
       return
-    answer = chat.match[1]
-    isYes = answer.charAt(0) == 'y' || answer.charAt(0) == 'o'
     deployment = deploymentMonitor.getDeployment()
-    if(isYes)
-      pulsarApi.runJob(deployment)
-      chat.send deployment + ' in progress'
-    else
-      chat.send deployment + ' removed'
-      deploymentMonitor.removeDeployment()
+    pulsarApi.runJob(deployment)
+    chat.send deployment + ' in progress'
+
+  robot.respond /cancel deploy$/i, (chat) ->
+    return unless robot.isAuthorized(chat)
+    if(!deploymentMonitor.hasDeployment())
+      chat.send 'No deployment to cancel'
+      return
+    deployment = deploymentMonitor.getDeployment()
+    chat.send deployment + ' cancelled'
+    deploymentMonitor.removeDeployment()


don't register application/json media type

--- src/draft3.coffee
+++ src/draft3.coffee
@@ -37,7 +37,7 @@

   constructor: (schemas...) ->
-    @references = {}
+    @uris = {}
     @media_types = {}
     @tests = {}
     @unresolved = {}
@@ -66,15 +66,16 @@
         delete @unresolved[ref]
         @register ref, found_schema
     if Object.keys(@unresolved).length > 0
-      refs = JSON.stringify(Object.keys(@unresolved))
-      throw new Error "Unresolvable $ref values: #{refs}"
+      pointers = (uri for key, {uri} of @unresolved)
+      throw new Error "Unresolvable $ref values: #{JSON.stringify pointers}"

     @compile(schema, context)

   register: (uri, schema) ->
-    @references[uri] = schema
+    @uris[uri] = schema
     if media_type = schema.mediaType
-      @media_types[media_type] = schema
+      if media_type != "application/json"
+        @media_types[media_type] = schema

   validate: (data) ->
     @schema("#").validate(data)
@@ -83,15 +84,16 @@
     if @tests[uri]
       validate: (data) =>
         valid: @tests[uri](data)
+        errors: ["Error report not implemented"]
       toJSON: (args...) =>
-        @references[uri]
+        @uris[uri]
     else
       throw new Error "No schema found for '#{uri}'"

   find: (arg) ->
     if @test_type "string", arg
       uri = escape(arg)
-      @references[uri]
+      @uris[uri]
     else if media_type = arg.mediaType
       @media_types[media_type]
     else
@@ -116,6 +118,7 @@
     # Here, we treat non-JSON-pointer fragments (such as "#user") as aliases.
     if schema.id && schema.id.indexOf("#") == 0
       uri = URI.resolve scope, schema.id
+      schema.id = uri
       @register uri, schema

     if @test_type "object", schema


fix main-nav top margin issue

fix main-nav top margin issue

--- src/scripts/models/mainNavViewModel.coffee
+++ src/scripts/models/mainNavViewModel.coffee
@@ -89,7 +89,7 @@
     $mainNav = $(element)
     $window = $(window)
     $accountSwitcher = $('account-switcher')
-    $navbar = $('nav.navbar')
+    $navbar = $('brand-bar, nav.navbar')

     calculateMainNavPosition = () =>
       originalTopPosition = 0


Don't fetch images.

Don't fetch images.

--- src/server/browser/DOMWindowFactory.coffee
+++ src/server/browser/DOMWindowFactory.coffee
@@ -10,7 +10,7 @@
     constructor : (@browser) ->
         @jsdom = noCacheRequire('jsdom')
         @jsdom.defaultDocumentFeatures =
-            FetchExternalResources : ['script', 'img', 'css', 'frame', 'link', 'iframe']
+            FetchExternalResources : ['script', 'css', 'frame', 'link', 'iframe']
             ProcessExternalResources : ['script', 'frame', 'iframe', 'css']
             MutationEvents : '2.0'
             QuerySelector : false
@@ -99,7 +99,7 @@
                 args.push('\n')
                 browser.emit 'ConsoleLog',
                     msg : args.join(' ')
-                if TESTS_RUNNING
+                if process.env.TESTS_RUNNING
                     console.log(args.join(' '))

     patchWindowMethods : (window) ->


Don't track any impressions or experiments for homepage entry hub if user is logged in (mobile web) (#4824)

Don't track any impressions or experiments for homepage entry hub if user is logged in (mobile web)

--- src/mobile/apps/home/client/view.coffee
+++ src/mobile/apps/home/client/view.coffee
@@ -21,13 +21,15 @@

   initialize: ->
     @collection = new ShowsFeed
-    if sd.HOMEPAGE_COLLECTION_HUB_ENTRYPOINTS_TEST == "experiment"
-      analytics.track("Impression", {
-        context_page: "Home",
-        context_module: "HubEntrypoint",
-        subject: "Featured Categories",
-      })
-    splitTest("homepage_collection_hub_entrypoints_test").view()
+
+    if !sd.CURRENT_USER
+      if sd.HOMEPAGE_COLLECTION_HUB_ENTRYPOINTS_TEST == "experiment"
+        analytics.track("Impression", {
+          context_page: "Home",
+          context_module: "HubEntrypoint",
+          subject: "Featured Categories",
+        })
+      splitTest("homepage_collection_hub_entrypoints_test").view()

     @slideshow = new Flickity '#carousel-track',
       cellAlign: 'left'


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/follow.coffee
+++ src/follow.coffee
@@ -58,6 +58,7 @@
   # calculate a velocity.

   enemy.I.velocity = enemy.I.direction.scale(4)
+  # now

   </pre></code>


Add links to real users, where possible

Add links to real users, where possible

--- src/github.coffee
+++ src/github.coffee
@@ -50,6 +50,16 @@

   saveNotifications = (notifications) ->
     robot.brain.set 'github-notifications', notifications
+
+  lookupUserWithGithub = (github) ->
+    users = getGithubUsers()
+    result = (user for user in users when user.github is github.login) if github
+    if result?.length is 1
+      return "<@#{result[0].id}>"
+    else if github
+      return github.login
+    else
+      return "Unassigned"

   notificationShouldFire = (notification) ->
     now = new Date
@@ -117,22 +127,20 @@
           return repo.pulls(pr.number).fetch()
         return
     .then ( prs ) ->
-      try
-        message = ""
-        for pr in prs when pr
-            message+= """
-              *[#{pr.title}]* +#{pr.additions} -#{pr.deletions}
-              #{pr.htmlUrl}
-              Updated: *#{moment(pr.updatedAt).fromNow()}*
-              Status: #{if pr.mergeable then "Ready for merge" else "Needs rebase"}
-              Assignee: #{ if pr.assignee? then "<@#{pr.assignee.login}>" else "Unassigned" }
-              \n
-            """
-      finally
-        if message.length is 0
-          message = "No matching pull requests found"
-
-        robot.messageRoom room, message
+      message = ""
+      for pr in prs when pr
+          message+= """
+            *[#{pr.title}]* +#{pr.additions} -#{pr.deletions}
+            #{pr.htmlUrl}
+            Updated: *#{moment(pr.updatedAt).fromNow()}*
+            Status: #{if pr.mergeable then "Ready for merge" else "Needs rebase"}
+            Assignee: #{lookupUserWithGithub pr.assignee}
+            \n
+          """
+      if message.length is 0
+        message = "No matching pull requests found"
+
+      robot.messageRoom room, message

   # Run a cron job that runs every minute, Monday-Friday
   new cronJob('1 * * * * 1-5', checkNotifications, null, true)


Update gitlab.coffee

Update gitlab.coffee

fixes #12

--- src/gitlab.coffee
+++ src/gitlab.coffee
@@ -101,7 +101,10 @@
             else if /^0+$/.test hook.after
               message = "#{bold(hook.user_name)} removed a tag (#{bold(tag)}) from #{bold(hook.repository.name)} (#{underline(hook.repository.homepage)})"
             else
-              message = "#{bold(hook.user_name)} pushed #{bold(hook.total_commits_count)} commits to tag (#{bold(tag)}) in #{bold(hook.repository.name)} (#{underline(hook.repository.homepage)})"
+              if hook.total_commits_count == 1
+                message = "#{bold(hook.user_name)} pushed #{bold(hook.total_commits_count)} commit to tag (#{bold(tag)}) in #{bold(hook.repository.name)} (#{underline(hook.repository.homepage)})"
+              else
+                message = "#{bold(hook.user_name)} pushed #{bold(hook.total_commits_count)} commits to tag (#{bold(tag)}) in #{bold(hook.repository.name)} (#{underline(hook.repository.homepage)})"
           else
             branch = hook.ref.split("/")[2..].join("/")
             # if the ref before the commit is 00000, this is a new branch


Add tokens to inspector

Add tokens to inspector

--- src/public/scripts/app.inspector.coffee
+++ src/public/scripts/app.inspector.coffee
@@ -298,6 +298,8 @@
 		die: ( ) ->
 			@scene.remove(@mesh)
 			@scene.remove(edge) for edge in @edges
+			@scene.remove(@tokenMesh)
+			@scene.remove(@tokenEdge)
 			@edges = []

 		# Updates this nodes' position and the lines to its connected nodes.
@@ -327,15 +329,39 @@
 						@edges.push(edge)
 						@scene.add(edge)

+			if @tokenMesh?
+				@scene.remove(@tokenEdge) if @tokenEdge?
+				geometry = new Three.Geometry()
+				geometry.vertices.push(@mesh.position, @tokenMesh.position)
+				material = new Three.LineDashedMaterial(color: 0xffff00)
+				@tokenEdge = new Three.Line(geometry, material)
+				@scene.add(@tokenEdge)
+
 		# Sets information of this node from a nodeInfo object. Used to update
 		# the node's state or connected peers.
 		#
 		# @param nodeInfo [Object] an object representing this node
 		#
 		setInfo: ( nodeInfo ) ->
+			if @tokenMesh?
+				@scene.remove(@tokenMesh)
+				delete @tokenMesh
+
+			if @tokenEdge?
+				@scene.remove(@tokenEdge)
+				delete @tokenEdge
+
 			@id = nodeInfo.id
 			@isSuperNode = nodeInfo.isSuperNode
 			@token = nodeInfo.token
+
+			if @token?
+				geometry = new Three.SphereGeometry(0.2, 6, 8)
+				material = new THREE.MeshLambertMaterial( color:0xffff00 )
+				@tokenMesh = new Three.Mesh(geometry, material)
+				@tokenMesh.position.set(@token.position[0], @token.position[1], @token.position[2])
+				@scene.add(@tokenMesh)
+
 			@peers = nodeInfo.peers

 			# Set supernodes to display as red, normal nodes as green.


added helper for routes

added helper for routes

--- vendor/assets/javascripts/angular-helpers.coffee
+++ vendor/assets/javascripts/angular-helpers.coffee
@@ -20,3 +20,21 @@
   constructor:($route, $xhr)->
     @setupXhr($xhr)
     @initRoutes $route, @routes()
+
+@resourceService = (serviceName, path, resourceTypes...)->
+	if resourceTypes.length is 0
+		resourceTypes.push 'index', 'create', 'update', 'destroy'
+	commandHash = {}
+	for type in resourceTypes
+		commandHash[type] = switch type
+			when 'index'
+				{ method:'GET', isArray:true }
+			when 'create'
+				{ method: 'POST' }
+			when 'update'
+				{ method: 'PUT' }
+			when 'destroy'
+				{ method: 'DELETE' }
+
+	angular.service serviceName, ($resource)->
+		$resource path, {}, commandHash


BuildExpressionFromJavascriptObject now takes array, cleans up array checking logic

--- javascripts/lib/math/build_expression_from_javascript_object.coffee
+++ javascripts/lib/math/build_expression_from_javascript_object.coffee
@@ -19,9 +19,9 @@

       process: (object_to_convert)->
         return @blank_builder.build() unless object_to_convert
-        if object_to_convert.length != undefined && typeof object_to_convert != "string"
+        if object_to_convert instanceof Array
           exp = @expression_builder.build()
-          @processExpression(exp, object_to_convert)
+          @processExpressionParts(exp, object_to_convert)
         else
           switch typeof(object_to_convert)
             when "number" then @number_builder.build(value: object_to_convert)
@@ -52,6 +52,7 @@

     BuildExpressionFromJavascriptObject.buildExpression = ->
       builder = BuildExpressionFromJavascriptObject.build()
-      builder.process(arguments)
+      arguments_as_array = Array.prototype.slice.call(arguments, 0)
+      builder.process(arguments_as_array)

     return BuildExpressionFromJavascriptObject


major cleanup in init and rendering code

--- src/math3d.coffee
+++ src/math3d.coffee
@@ -211,11 +211,18 @@
             if error
                 return @opts.callback? error

-            @attachToDom @opts.parent
-
             if @_init
                 return @opts.callback? undefined, @
             @_init = true
+
+            # IMPORTANT: There is a major bug in three.js -- if you make the width below more than .5 of the window
+            # width, then after 8 3d renders, things get foobared in WebGL mode.  This happens even with the simplest
+            # demo using the basic cube example from their site with R68.  It even sometimes happens with this workaround, but
+            # at least retrying a few times can fix it.
+            @opts.width ?= document.documentElement.clientWidth/2
+            @opts.height ?= @opts.width*2/3
+
+            @attachToDom @opts.parent

             # setup aspect ratio stuff
             aspectRatio = @aspectRatio = new THREE.Vector3 @opts.aspect_ratio...
@@ -238,21 +245,9 @@

             # functions in change hooks will be run when the controls
             # recieve a change event
-            @changeHooks = []
-
-            # IMPORTANT: There is a major bug in three.js -- if you make the width below more than .5 of the window
-            # width, then after 8 3d renders, things get foobared in WebGL mode.  This happens even with the simplest
-            # demo using the basic cube example from their site with R68.  It even sometimes happens with this workaround, but
-            # at least retrying a few times can fix it.
-            @opts.width ?= document.documentElement["clientWidth"]/2
-            @opts.height ?= @opts.width*2/3
-
-            @opts.renderer ?= _defaultRendererType
-            @setDynamicRenderer()
+            @renderHooks = []
+
             @init_on_mouseover()
-
-            # add a bunch of lights
-            @init_light()

             @opts.callback? undefined, @

@@ -263,10 +258,22 @@
         @setFrame()
         @setCamera()
         @setOrbitControls()
-
-        @runChangeHooks()
-
-        @render_scene()
+        @setLight()
+
+        # borrow the renderer to do the initial render
+        owner = _sceneUsingRenderer
+        wasDynamic = false
+        if owner? and owner.rendererType is 'dynamic'
+            wasDynamic = true
+
+        @opts.renderer ?= _defaultRendererType
+        @setDynamicRenderer() # used to do the first render
+
+        @setStaticRenderer()
+
+        # then give it back to whoever had it (if they were using it)
+        if wasDynamic
+            owner.setDynamicRenderer()

         # possibly show the canvas warning.
         if @opts.renderer is 'canvas'
@@ -278,6 +285,7 @@
         else
             @element = document.createElement 'span'
             @element.className = 'math-3d-viewer'
+            @element.style.display = 'inline-block'

         parentElement.appendChild @element

@@ -289,28 +297,28 @@
         @renderer = getRenderer @, @opts.renderer
         @rendererType = 'dynamic'

+        @renderer.setClearColor @background, 1
+        @renderer.setSize @opts.width, @opts.height
+
+        @controls.enabled = true
+
+        if @opts.spin
+            @animate render: false
+
+        @renderScene true
+
         # remove the current renderer (if it exists)
         removeElement @element.lastChild

         # place renderer in correct place in the DOM
         @element.appendChild @renderer.domElement
-
-        @renderer.setClearColor @background, 1
-        @renderer.setSize @opts.width, @opts.height
-
-        @controls?.enable = true
-
-        if @opts.spin
-            @animate render: false
-
-        @render_scene true

     setStaticRenderer: ->
         if @rendererType is 'static'
             # already have it
             return

-        @controls?.enable = false
+        @controls.enabled = false

         if not @staticImage?
             @staticImage = document.createElement 'img'
@@ -344,22 +352,6 @@
             type    : 'png'      # 'png' or 'jpeg' or 'webp' (the best)
             quality : undefined   # 1 is best quality; 0 is worst; only applies for jpeg or webp
         @renderer.domElement.toDataURL "image/#{opts.type}", opts.quality
-
-    init_light: (color= 0xffffff) ->
-        ambient = new THREE.AmbientLight(0x404040)
-        @scene.add ambient
-
-        color = 0xffffff
-        d     = 10000000
-        intensity = 0.5
-
-        for p in [[d,d,d], [d,d,-d], [d,-d,d], [d,-d,-d],[-d,d,d], [-d,d,-d], [-d,-d,d], [-d,-d,-d]]
-            directionalLight = new THREE.DirectionalLight color, intensity
-            directionalLight.position.set(p...).normalize()
-            @scene.add directionalLight
-
-        @light = new THREE.PointLight color
-        @light.position.set 0, d, 0

     updateBoundingBox: (obj) ->
         obj.geometry.computeBoundingBox()
@@ -403,7 +395,7 @@
             @_text = []

             up = new THREE.Vector3()
-            @changeHooks.push =>
+            @renderHooks.push =>
                 up.set(0, 1, 0).applyQuaternion @camera.quaternion
                 for text in @_text
                     text.up.copy up
@@ -541,7 +533,7 @@
     _addSpherePoint: (opts) ->
         if not @_points?
             @_points = []
-            @changeHooks.push =>
+            @renderHooks.push =>
                 for point in @_points
                     scale = @camera.position.distanceTo point.position
                     point.scale.set scale, scale, scale
@@ -766,6 +758,20 @@
                 addLabel [avg.x, max.y, min.z], "x = #{format avg.x}"
                 addLabel [min.x, max.y, min.z], format(min.x)

+    setLight: (opts) ->
+        opts = defaults opts,
+            color: 0xffffff
+            intensity: 0.5
+
+        ambient = new THREE.AmbientLight opts.color
+        @scene.add ambient
+
+        for coord in ['x', 'y', 'z']
+            for sign in [1, -1]
+                directionalLight = new THREE.DirectionalLight opts.color, opts.intensity
+                directionalLight.position.set(0,0,0)[coord] = sign
+                @scene.add directionalLight
+
     setCamera: ->
         view_angle = 45
         aspect     = @opts.width/@opts.height
@@ -784,16 +790,9 @@

         @scene.add @camera

-    runChangeHooks: ->
-        if @rendererType is 'dynamic'
-            for hook in @changeHooks
-                hook()
-
-            @renderer.render @scene, @camera
-
     setOrbitControls: ->
         # set up camera controls
-        @controls = new OrbitControls @camera, @renderer.domElement
+        @controls = new OrbitControls @camera, @element

         @controls.target = @center

@@ -808,7 +807,7 @@
                 @controls.autoRotateSpeed = @opts.spin
             @controls.autoRotate = true

-        @controls.addEventListener 'change', => @runChangeHooks()
+        @controls.addEventListener 'change', => @renderScene true

     animate: (opts = {}) ->
         opts = defaults opts,
@@ -828,9 +827,9 @@
             return

         if @element.offsetWidth <= 0 and @element.offsetWidth <= 0
-            if @opts.stop_when_gone? and not contains document, @opts.stop_when_gone
+            if @opts.stop_when_gone? and not document.contains(@opts.stop_when_gone)
                 @_animate_started = false
-            else if not contains document, @element
+            else if not document.contains(@element)
                 setTimeout (=> @_animate opts), 5000
             else
                 setTimeout (=> @_animate opts), 1000
@@ -844,7 +843,7 @@
             @_stop_animating = false
             @_animate_started = false
             return
-        @render_scene opts.render
+        @renderScene opts.render
         delete opts.render
         f = =>
             requestAnimationFrame (=> @_animate opts)
@@ -853,35 +852,37 @@
         else
             f()

-    render_scene: (force = false) ->
+    renderScene: (force = false) ->
+        if @_rendering? and @_rendering
+            # already in the process of rendering
+            return
+
         if @rendererType isnt 'dynamic'
-            # if we don't have the renderer, swap it in, make a static image,
-            # then give it back to whoever had it.
-            owner = _sceneUsingRenderer
-            @setDynamicRenderer()
-            @setStaticRenderer()
-            owner?.setDynamicRenderer()
+            # scene's are only rendered when they are dynamic
             return

         if not @camera?
             return # nothing to do yet

-        @controls?.update()
-
-        pos = @camera.position
-        if not @_last_pos?
-            new_pos = true
-            @_last_pos = pos.clone()
-        else if @_last_pos.distanceToSquared(pos) > .05
-            new_pos = true
-            @_last_pos.copy pos
-        else
-            new_pos = false
-
-        if not new_pos and not force
-            return
-
-        @renderer.render @scene, @camera
+        @_rendering = true
+
+        if not force
+            position = @camera.position
+            if not @_lastPosition?
+                force = true
+                @_lastPosition = position.clone()
+                #@_renderDistance = @minDim*@minDim/10000
+            else if @_lastPosition.distanceToSquared(position) > 0.05
+                force = true
+                @_lastPosition.copy position
+
+        if force
+            @controls.update()
+            for hook in @renderHooks
+                hook()
+            @renderer.render @scene, @camera
+
+        @_rendering = false

 math3d.render_3d_scene = (opts) ->
     opts = defaults opts,


corrected typo

corrected typo

--- config/main.dev.coffee
+++ config/main.dev.coffee
@@ -121,7 +121,7 @@
   mq            :
     host        : 'web-dev.in.koding.com'
     port        : 5672
-    apiAdress   : "web-dev.in.koding.com"
+    apiAddress  : "web-dev.in.koding.com"
     apiPort     : 15672
     login       : 'guest'
     componentUser: "guest"


Config: ngrok route made mainUri for dev

--- config/main.dev.coffee
+++ config/main.dev.coffee
@@ -160,7 +160,7 @@
     socialApiUri      : "/xhr"
     apiUri            : null
     sourceMapsUri     : "/sourcemaps"
-    mainUri           : null
+    mainUri           : "http://koding-#{process.env.USER}.ngrok.com"
     broker            : uri  : "/subscribe"
     appsUri           : "/appsproxy"
     uploadsUri        : 'https://koding-uploads.s3.amazonaws.com'


Round the numbers

Round the numbers

--- spec/engine.coffee
+++ spec/engine.coffee
@@ -73,9 +73,15 @@
     it 'after solving the second button should be taller', (done) ->
       gss.onSolved = (values) ->
         chai.expect(values).to.be.an 'object'
-        chai.expect(values['#button3[width]']).to.equal values['#button4[height]']
-        chai.expect(button3.getBoundingClientRect().width).to.equal values['#button3[width]']
-        chai.expect(button4.getBoundingClientRect().height).to.equal values['#button4[height]']
+        rounded =
+          calc_b4h: Math.round values['#button4[height]']
+          calc_b3w: Math.round values['#button3[width]']
+          real_b4h: button4.getBoundingClientRect().height
+          real_b3w: button3.getBoundingClientRect().width
+
+        chai.expect(rounded.calc_b3w).to.equal rounded.calc_b4h
+        chai.expect(rounded.real_b3w).to.equal rounded.calc_b3w
+        chai.expect(rounded.real_b4h).to.equal rounded.calc_b4h
         chai.expect(button4.getBoundingClientRect().height).to.be.above button3.getBoundingClientRect().height
         done()
       gss.onError = (error) ->


Pass the module's methods to its initialize method.

This allows modules to use their own methods in a similar way that the
prompt file and other modules will use their APIs. Hopefully this will
make module design a bit clearer (and less unweildy).

Some property locking is planned for a future commit.

--- src/module.coffee
+++ src/module.coffee
@@ -5,7 +5,7 @@
 class _Module
   constructor: (@impromptu, @factory, @name, initialize) ->
     @_methods = {}
-    initialize.call @, Impromptu
+    initialize.call @, Impromptu, @_methods

   register: (key, options) ->
     # Cache responses using the instance cache by default.


let userStatistics show errors to get root cause

--- server/publish/userStatistics.coffee
+++ server/publish/userStatistics.coffee
@@ -94,7 +94,19 @@
 					shift.teams.filter (t) ->
 						(userId in t.participants.map (u) -> u._id) ||
 						(userId in t.pending.map (u) -> u._id) ||
-						(userId in t.declined.map (u) -> u._id)
+						(userId in t.declined.map (u) ->
+							if u?
+								u._id
+							else
+								console.log 'ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR'
+								console.log 'ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR'
+								console.log 'ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR'
+								console.log u
+								console.log t
+								console.log 'ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR'
+								console.log 'ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR'
+								console.log 'ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR'
+						)
 					.length

 				countTeamsApproved = (shift) ->


Adding more snippets

Adding more snippets

--- snippets/wait.cson
+++ snippets/wait.cson
@@ -10,7 +10,7 @@

   'wait1.5':
     'prefix': 'wait "time" seconds for a link with the text content of alias "content" to be present'
-    'body': 'wait "$1" seconds for a link with the text content of alias "$2" to be present'
+    'body': 'wait "$1" seconds for a link with the text content of alias "$2" to be present'

   'wait2':
     'prefix': 'wait "time" seconds for the element found by "identifier" to be present'


Fixed misleading log about animation pause/resume

--- source/Main.coffee
+++ source/Main.coffee
@@ -35,9 +35,9 @@

 resumeAnimating = ->
 	if not animating
+		console.log("Resumed animation")
 		animating = true
 		tick()
-		console.log("Resumed animation")

 pauseAnimating = ->
 	if animating


Better Coverage results

Better Coverage results

--- src/unit/cli.coffee
+++ src/unit/cli.coffee
@@ -4,8 +4,6 @@
 pathUtils = require 'path'
 cp = require 'child_process'
 fs = require 'fs'
-unitsStack = require './stack'
-require './index'

 [_, _, path, optsArgv...] = process.argv

@@ -51,6 +49,9 @@
     else
         require requirePath

+unitsStack = require './stack'
+require './index'
+
 if pathIsFile
     runTestFile path
 else


add b-on attribute

add b-on attribute

--- src/render.coffee
+++ src/render.coffee
@@ -35,8 +35,15 @@
   name = element.name
   children = element.children.slice()
   attributes = []
+  events = []
   element.attributes.forEach (attr) ->
     switch attr.name
+      when 'b-on'
+        attr.value.split(/\s*,\s*/).forEach (a) ->
+          [n, v] = a.split /\s*:\s*/
+          events.push
+            name: n
+            value: get v, context
       when 'b-attr'
         attr.value.split(/\s*,\s*/).forEach (a) ->
           [n, v] = a.split /\s*:\s*/
@@ -58,7 +65,7 @@
   children = children.reduce (c, child) ->
     c.concat renderElement child, context
   , []
-  [{ type, name, attributes, children }]
+  [{ type, name, attributes, children, events }]

 renderElements = (element, context) ->
   type = element.type


Added feature flag for tracking tags

Added feature flag for tracking tags

--- src/services/theming/theming.svc.coffee
+++ src/services/theming/theming.svc.coffee
@@ -105,6 +105,8 @@
             bls: "Live"
         timePeriod:
           showSlider: true
+        tagging:
+          enableFilterTags: false

     #=======================================


revert widget update (needs to wait for the server's response)

--- src/services/widgets/widgets.svc.coffee
+++ src/services/widgets/widgets.svc.coffee
@@ -242,13 +242,13 @@
             else
               request = $http.put(ImpacRoutes.widgets.update(dashboard.id, widget.id), data)

-            # No need to wait for the server's response to update the widget in the frontend
-            angular.extend widget, opts
-            deferred.resolve(widget)
-
             request.then(
-              null
-              (error) -> $log.error("ImpacWidgetsSvc: cannot update widget: #{widget.id}")
+              (success) ->
+                angular.extend widget, success.data
+                deferred.resolve(widget)
+              (error) ->
+                $log.error("ImpacWidgetsSvc: cannot update widget: #{widget.id}")
+                deferred.reject(error)
             )

         (error) ->


Add some clarification regarding city/municipality

--- src/router.coffee
+++ src/router.coffee
@@ -52,6 +52,17 @@
                 context.query = @processQuery fullUri.search(true)
                 if context.query.map?
                     p13n.setMapBackgroundLayer context.query.map
+                # Explanation of the difference of municipality vs. city query parameters.
+                # ------------------------------------------------------------------------
+                # The city parameter can be used by a city to create a link to
+                # the application with the p13n city pre-selected.
+                # The municipality parameter always overrides any p13n cities
+                # and so can be used to create links with explicit
+                # municipality filtering regardless of the user's preferences.
+                #
+                # For historical reasons, the embed urls use 'city', although
+                # the embeds should never load or save any persistent p13n
+                # values.
                 if context.query.city?
                     if window.isEmbedded == true
                         # We do not want the embeds to affect the users


Change time point to volume number.

Change time point to volume number.

--- test/e2e/sessionDetailSpec.coffee
+++ test/e2e/sessionDetailSpec.coffee
@@ -3,23 +3,25 @@
 Page = require './helpers/page'

 class SessionDetailPage extends Page
-  # @param time_point the time point
-  # @returns a promise resolving to the given time point
-  #   {download: ElementFinder, display: ElementFinder}
+  # Note: this function should return a promise, but for an unknown
+  # reason the return value is the resolved object instead.
+  #
+  # @param number the volume number
+  # @returns the {download: ElementFinder, display: ElementFinder}
   #   associative object, where each ElementFinder resolves
   #   to the respective button
-  scanImageButtons: (time_point) ->
+  scanImageButtons: (number) ->
     # The locator for the image select button group element.
-    # Since the time point is one-based, the zero-based image
-    # select index is one less than the time point.
-    locator = By.repeater('image in session.scans[0].images').row(time_point - 1)
-
+    # Since the tvolume number is one-based, the zero-based
+    # image select index is one less than the volume number.
+    locator = By.repeater('volume in scan.volumes').row(number - 1)
     # Find the image select button group element, then...
-    @find(locator).then (div) ->
-      # ...return the associative object which contains the
-      # both button ElementFinders.
-      download: div.element(By.css('.glyphicon-download'))
-      open: div.element(By.css('.glyphicon-eye-open'))
+    @find(locator).then (elt) ->
+      if elt
+        # ...make an associative object which references the
+        # button ElementFinders.
+        download: elt.element(By.css('.glyphicon-download'))
+        open: elt.element(By.css('.glyphicon-eye-open'))

   # Loads the image by clicking the given download button.
   #
@@ -58,12 +60,13 @@
   chart: ->
     @find('//qi-intensity-chart//nvd3-line-chart')

+
 describe 'E2E Testing Session Detail', ->
-  # The Sarcoma001 Session01 time point 20. This is the time point for
-  # which there is a test fixture scan image file. The seed helper
-  # function called from the protractor config links the test fixture
-  # data directory to _public/data subdirectory.
-  TEST_TIME_POINT = 20
+  # The Sarcoma001 Session01 volume number for which there is a test
+  # fixture scan image file. The seed helper function called from the
+  # protractor config links _public/data to the test fixture data
+  # directory.
+  TEST_VOL_NBR = 20

   page = null

@@ -98,7 +101,10 @@

     it 'should load the image', ->
       # Find the download/display button pair, then...
-      page.scanImageButtons(TEST_TIME_POINT).then (btnGroup) ->
+      #
+      # Work around the anomaly described in the scanImageButtons function.
+      #page.scanImageButtons(TEST_VOL_NBR).then (btnGroup) ->
+        btnGroup = page.scanImageButtons(TEST_VOL_NBR)
         # The download button should be displayed.
         download = btnGroup.download
         expect(download.isDisplayed(), 'The download button is initially hidden')


fix debug name

fix debug name

--- src/server.coffee
+++ src/server.coffee
@@ -7,7 +7,7 @@
 logger = require("morgan")
 cookieParser = require("cookie-parser")
 bodyParser = require("body-parser")
-debug = require("debug")("upgrade-contacts")
+debug = require("debug")("react-express-template")
 require("babel/register")

 dist = path.join(__dirname, '/../dist')


store mongoose connection as 'mongoose' not 'db' on app

--- src/server.coffee
+++ src/server.coffee
@@ -46,18 +46,18 @@
 		log.silly "Configuration", CONFIG
 		@app = Express()
 		# Start DB
-		@app.db = Mongoose.createConnection(
+		@app.mongoose = Mongoose.createConnection(
 			CONFIG.mongoURI
 			CONFIG.mongoServerOptions
 		)
-		if not @app.db
+		if not @app.mongoose
 			throw new Error("Must set the MongoDB connection for API")
-		@app.db.on 'error', (e) =>
+		@app.mongoose.on 'error', (e) =>
 			log.error "ERROR starting MongoDB"
 			throw e
 		# Schemo!
 		@app.schemo = new Schemo(
-			mongoose: @app.db
+			mongoose: @app.mongoose
 			baseURI: CONFIG.baseURI
 			apiPrefix: CONFIG.apiPrefix
 			schemaPrefix: CONFIG.schemaPrefix


server scan immediately

server scan immediately

--- src/server.coffee
+++ src/server.coffee
@@ -16,4 +16,4 @@
       cb s

 module.exports = (client, creds, cb) ->
-  s = new Server client, creds, cb#() -> s.scanAll (s) -> s
+  s = new Server client, creds, () -> s.scanAll (s) -> cb s


Make planes their own database as such

Make planes their own database as such

--- src/scripts/airline_tycoon.cjsx
+++ src/scripts/airline_tycoon.cjsx
@@ -8,24 +8,30 @@
   name: 'Blue'
   color: blue
   planes: [
-    {name: 'Plane1', flights: 0, location: 'DUB'},
-    {name: 'Plane2', flights: 0, location: 'DUB'},
-    {name: 'Plane3', flights: 0, location: 'DUB'}
   ]

 player2 =
   color: pink
   name: 'Pink'
   planes: [
-    {name: 'Plane4', flights: 0, location: 'NYC'},
-    {name: 'Plane5', flights: 0, location: 'NYC'},
-    {name: 'Plane6', flights: 0, location: 'NYC'}
   ]

 nobody =
   color: grey
   name: 'Nobody'
   planes: []
+
+planes = [
+  {name: 'Plane1', flights: 0, location: 'DUB', owner: player1},
+  {name: 'Plane2', flights: 0, location: 'DUB', owner: player1},
+  {name: 'Plane3', flights: 0, location: 'DUB', owner: player1},
+  {name: 'Plane4', flights: 0, location: 'NYC', owner: player2},
+  {name: 'Plane5', flights: 0, location: 'NYC', owner: player2},
+  {name: 'Plane6', flights: 0, location: 'NYC', owner: player2}
+]
+
+planes_at = (location) ->
+  plane for plane in planes when plane.location == location

 # Duplicating name/key as you need unique keys, but can't do @props.key for name...
 airports = [
@@ -67,7 +73,7 @@

{@props.name} - {@props.owner.name}

-      <PlaneList planes={plane for plane in @props.owner.planes when plane.location == @props.name} />
+      <PlaneList planes={planes_at(@props.name)} />

 Route = React.createClass
@@ -83,10 +89,11 @@

{@props.name}

+      <PlaneList planes={planes_at(@props.name)} />

 PlaneList = React.createClass
   render: ->
-    planes = (
{plane.name}
 for plane in @props.planes)
+    plane_components = (
{plane.name}
 for plane in @props.planes)

-
{planes}

+
{plane_components}


Use correct disabled fields for bootstrap, work with the ajax event disables

--- js/inner.coffee
+++ js/inner.coffee
@@ -395,7 +395,7 @@

     # disable the submit button to prevent repeated clicks
     button = $('.submit-button')
-    button.attr "disabled", "disabled"
+    button.addClass "disabled"

     Stripe.createToken {
       number: $('.card-number').val()
@@ -404,7 +404,7 @@
       exp_year: $('.card-expiry-year').val()
     }, (status, response) =>
       if response.error
-        button.removeAttr "disabled", "disabled"
+        button.removeClass "disabled"
         VM.setErrorMessage response.error.message
       else
         @recordStripeTransaction event, response # TODO: add the plan


validate hashes of files being uploaded

validate hashes of files being uploaded

--- web.coffee
+++ web.coffee
@@ -1,4 +1,5 @@
 coffee   = require("coffee-script")
+crypto   = require("crypto")
 express  = require("express")
 fs       = require("fs")
 manifest = require("manifest")
@@ -26,8 +27,16 @@
     get.on "end",          -> res.end()

 app.post "/file/:hash", (req, res) ->
-  storage.create_stream "/hash/#{req.params.hash}", fs.createReadStream(req.files.data.path), (err) ->
-    res.send("ok")
+  sha  = crypto.createHash("sha256")
+  file = fs.createReadStream(req.files.data.path)
+  file.on "data", (data) ->
+    sha.update data
+  file.on "end", ->
+    if req.params.hash == sha.digest("hex")
+      storage.create_stream "/hash/#{req.params.hash}", fs.createReadStream(req.files.data.path), (err) ->
+        res.send "ok"
+    else
+      res.send "file does not match hash", 403

 app.post "/manifest", (req, res) ->
   manifest.init(JSON.parse(req.body.manifest)).save (err, id) ->


+ mGet/mGetSync to get multi keys

+ mGet/mGetSync to get multi keys

--- src/abstract-nosql.coffee
+++ src/abstract-nosql.coffee
@@ -66,6 +66,20 @@
       return result
     throw new NotImplementedError()

+  mGetSync: (keys, options) ->
+    if @_mGetSync
+      options = {} unless options?
+      arr = @_mGetSync(key, options)
+      i = 0
+      result = []
+      while i < arr.length
+        result.push
+          key: arr[i]
+          value: arr[++i]
+        i++
+      return result
+    throw new NotImplementedError()
+
   putSync: (key, value, options) ->
     if @_putSync
       options = {} unless options?
@@ -173,6 +187,39 @@
         else
           callback null, true

+  _mGetSync: (keys, options) ->
+    if @_getSync
+      result = []
+      for key in keys
+        value = @_getSync(key, options)
+        result.push key, value
+      return result
+    else
+      throw new NotImplementedError('_mGetSync: _getSync is not implemented.')
+
+  _mGet: (keys, options, callback) ->
+    that = this
+    if @_getSync
+      setImmediate ->
+        result = undefined
+        try
+          result = that._mGetSync(keys, options)
+        catch err
+          callback err
+          return
+        callback null, result
+    else if keys.length > 0
+      result = []
+      i = 0
+      readNext = (err, value)->
+        return callback(err) if err
+        result.push keys[i], value
+        i++
+        return callback(null, result) if i >= keys.length
+        @_get key[i], options, readNext
+      @_get keys[i], options, readNext
+    else
+      setImmediate callback

   _get: (key, options, callback) ->
     that = this
@@ -309,16 +356,40 @@
     else
       @isExistsSync key, options

+  mGet: (keys, options, callback) ->
+    err = undefined
+    if typeof options is "function"
+      callback = options
+      options = {}
+    else
+      options = {} unless options?
+    if callback
+      @_mGet keys, options, (err, arr)->
+        return callback(err) if err
+        i = 0
+        result = []
+        while i < arr.length
+          result.push
+            key: arr[i]
+            value: arr[++i]
+          i++
+        callback null, result
+    else
+      @mGetSync keys, options
+
   get: (key, options, callback) ->
     err = undefined
-    callback = options  if typeof options is "function"
+    if typeof options is "function"
+      callback = options
+      options = {}
+    else
+      options = {} unless options?
     if err = @_checkKey(key, "key", @_isBuffer)
       if callback
         return callback(err)
       else
         throw err
     key = String(key)  unless @_isBuffer(key)
-    options = {}  unless typeof options is "object"
     options.asBuffer = options.asBuffer isnt false
     if callback
       @_get key, options, callback


commit view value after update view value

--- src/angular-viacep.coffee
+++ src/angular-viacep.coffee
@@ -66,7 +66,9 @@

       _fillAddress = (address) ->
         for key in _validKeys
-          _mappers[key].$setViewValue(address[key]) if _mappers[key] != undefined
+          if _mappers[key] != undefined
+            _mappers[key].$setViewValue(address[key])
+            _mappers[key].$$commitViewValue()

       _get = (cepValue) ->
         if _isValidCep(cepValue)


add structure for video chat / persistant messages / add user to chat

--- src/chat_room_view.coffee
+++ src/chat_room_view.coffee
@@ -33,18 +33,15 @@

-

-

-
-
-
-

-

-
-
-
-

-

+

+

+

+

+

+

+

+

+

           <textarea class="field test" placeholder="Enter Message...">


artik533s.coffee: Discontinue board

artik533s.coffee: Discontinue board

We will no longer be offering OS updates for this board

Changelog-entry: Discontinue the artik533s board
Signed-off-by: Florin Sarbu <3a837a37a2ac9072a088641ec330e0e5d5b86f4e@balena.io>

--- artik533s.coffee
+++ artik533s.coffee
@@ -19,7 +19,7 @@
 	aliases: [ 'artik533s' ]
 	name: 'Samsung Artik 530s 1G'
 	arch: 'armv7hf'
-	state: 'experimental'
+	state: 'discontinued'

 	stateInstructions:
 		postProvisioning: postProvisioningInstructions


First selectors, id, class, tag...

First selectors, id, class, tag...

--- spec/1_core.coffee
+++ spec/1_core.coffee
@@ -1,5 +1,40 @@
 describe "Core", ->
+
+  body = undefined
+
+  beforeEach ->
+    body = $$(document.body)
+    document.body.innerHTML = """

"""
+

   it "is healthy", ->
     expect(Quo).toBeTruthy()
     expect($$).toBeTruthy()
+
+
+  it "select body", ->
+    expect(body).toBeTruthy()
+    expect(body.length > 0).toBeTruthy()
+
+
+  it "select by tag element", ->
+    tag = $$ "section"
+    expect(tag).toBeTruthy()
+    expect(tag.length > 0).toBeTruthy()
+
+
+  it "select by Id", ->
+    id = $$ "#quo"
+    expect(id).toBeTruthy()
+    expect(id.length > 0).toBeTruthy()
+
+
+  it "select by className", ->
+    className = $$ ".quo"
+    expect(className).toBeTruthy()
+    expect(className.length > 0).toBeTruthy()
+
+
+  it "if select doesn't exists array is empty.", ->
+    el = $$ "article"
+    expect(el.length is 0).toBeTruthy()


Remove whitespace

Remove whitespace

--- src/coffee/obscura.coffee
+++ src/coffee/obscura.coffee
@@ -82,9 +82,7 @@
 				newScale.h = scale.h

 			@dimensions = newScale unless crop
-
-
-
+
 		@load(0,0,newScale.w,newScale.h)
 		@context.restore()
 		return @


Added language highlighter

Added language highlighter

--- PHPGuzzleCodeGenerator.coffee
+++ PHPGuzzleCodeGenerator.coffee
@@ -125,5 +125,6 @@
 PHPGuzzleCodeGenerator.title =
     "PHP (Guzzle)"
 PHPGuzzleCodeGenerator.fileExtension = "php"
+PHPGuzzleCodeGenerator.languageHighlighter = "php"

 registerCodeGenerator PHPGuzzleCodeGenerator


feature: updated /package/xyz requests, it should now include the list of nodes where the package is installed

Signed-off-by: Dominik Richter <b7323add517e187efe0283b76511b7ed762bfe13@gmail.com>

--- test/spec/controllers/main.coffee
+++ test/spec/controllers/main.coffee
@@ -128,7 +128,10 @@
         "version": "0.6.15-2ubuntu9",
         "architecture": "amd64",
         "provider": "apt",
-        "archive": "precise"
+        "archive": "precise",
+        "nodes": [
+          'my1.full.fqdn'
+        ]
       }
     @httpBackend.whenGET('/v1/package/'+id).respond(r)
     @httpBackend.expectGET('/v1/package/'+id)
@@ -145,3 +148,4 @@
     expect(p.architecture).toBe(r.architecture)
     expect(p.provider).toBe(r.provider)
     expect(p.archive).toBe(r.archive)
+    expect(p.nodes).toBe(r.nodes)


add another test for addclass to check correct nodeType

--- test/spec/manipulationSpec.coffee
+++ test/spec/manipulationSpec.coffee
@@ -11,6 +11,7 @@
     $li_second = $('li#second')
     $input = $('#name')

+
   describe 'text()', ->
     it 'should not fail when called on an empty set', ->
       expect(-> $('#noexists').text()).not.toThrow()
@@ -24,6 +25,7 @@
     it 'should set the textContent of an element', ->
       $li_first.text('one')
       expect($li_first.text()).toEqual('one')
+

   describe 'html()', ->
     it 'should not fail when called on an empty set', ->
@@ -41,6 +43,7 @@
       $li_second.html(content)
       expect($li_second.html()).toEqual(content)

+
   describe 'val()', ->
     it 'should not fail when called on an empty set', ->
       expect(-> $('#noexists').val()).not.toThrow()
@@ -56,6 +59,7 @@
       content = 'changed'
       $input.val(content)
       expect($input[0].value).toEqual(content)
+

   describe 'addClass()', ->
     it 'should not fail when called on an empty set', ->
@@ -83,6 +87,10 @@
       expect(-> $li_second.addClass([])).not.toThrow()
       expect(-> $li_second.addClass({})).not.toThrow()

+    it 'should only add a class if the nodeType is 1 (ELEMENT_NODE)', ->
+      textNode = $($li_first[0].firstChild) # nodeType is 3
+      expect(textNode.addClass('text_node')).toEqual(textNode)
+
     it 'should do nothing when called with wierd or without arguments', ->
       currentClasses = getClass($li_first)
       $li_first.addClass()
@@ -142,6 +150,7 @@
       $li_second[0].className = 'has multiple classes'
       $li_second.removeClass(0)
       expect(getClass($li_second)).toEqual([''])
+

   describe 'hasClass()', ->
     it 'should not fail when called on an empty set', ->


Another fix for interaction between react and qtip

--- resources/assets/coffee/react/profile-page/achievement-badge.coffee
+++ resources/assets/coffee/react/profile-page/achievement-badge.coffee
@@ -20,14 +20,25 @@

 class ProfilePage.AchievementBadge extends React.Component
   onMouseOver: (event) =>
-    name = event.target.getAttribute 'data-tooltip-target'
+    el = event.currentTarget
+    id = el.getAttribute 'data-tooltip-target'

+    return if el._loadedTooltipId == id
+
+    content = $(".js-tooltip-achievement--content[data-tooltip-id='#{id}']").first().clone()
+
+    if el._loadedTooltipId?
+      el._loadedTooltipId = id
+      $(el).qtip 'set', 'content.text': content
+      return
+
+    el._loadedTooltipId = id
     classes = 'qtip tooltip-achievement'
     classes += ' tooltip-achievement--locked' if !@props.userAchievement?

     options =
       overwrite: false
-      content: $(".js-tooltip-achievement--content[data-tooltip-id='#{name}']").clone()
+      content: content
       position:
         my: 'bottom center'
         at: 'top center'
@@ -47,7 +58,7 @@
           width: 10
           height: 8

-    $(event.target).qtip options, event
+    $(el).qtip options, event

   iconUrl: =>


:lipstick:

:lipstick:

Make it not suck.

--- src/display-buffer.coffee
+++ src/display-buffer.coffee
@@ -756,11 +756,14 @@
       return

     marker = @getMarker(marker.id)
-    @decorationMarkerDestroyedSubscriptions[marker.id] ?= @subscribe marker, 'destroyed', => @removeAllDecorationsForMarker(marker)
-
-    subscription = @subscribe marker, 'changed', (event) => @emit('decoration-changed', marker, decoration, event)
-    @decorationMarkerChangedSubscriptions[marker.id] ?= []
-    @decorationMarkerChangedSubscriptions[marker.id].push {decoration, subscription}
+
+    @decorationMarkerDestroyedSubscriptions[marker.id] ?= @subscribe marker, 'destroyed', =>
+      @removeAllDecorationsForMarker(marker)
+
+    @decorationMarkerChangedSubscriptions[marker.id] ?= @subscribe marker, 'changed', (event) =>
+      decorations = @decorationsByMarkerId[marker.id]
+      for decoration in decorations
+        @emit 'decoration-changed', marker, decoration, event

     @decorationsByMarkerId[marker.id] ?= []
     @decorationsByMarkerId[marker.id].push(decoration)
@@ -771,13 +774,11 @@
       console.warn 'A decoration cannot be removed from a null marker'
       return

-    return unless @decorationMarkerSubscriptions[marker.id]?
-
-    decorations = @decorationsByMarkerId[marker.id]
+    return unless decorations = @decorationsByMarkerId[marker.id]
+
     for i in [decorations.length - 1..0]
       decoration = decorations[i]
       if @decorationMatchesPattern(decoration, decorationPattern)
-        @unsubscribeDecorationFromMarker(marker, decoration)
         decorations.splice(i, 1)
         @emit 'decoration-removed', marker, decoration

@@ -790,26 +791,12 @@
     @removedAllMarkerDecorations(marker)

   removedAllMarkerDecorations: (marker) ->
+    @decorationMarkerChangedSubscriptions[marker.id].off()
     @decorationMarkerDestroyedSubscriptions[marker.id].off()
-
-    for subscriptionObject in @decorationMarkerChangedSubscriptions[marker.id]
-      subscriptionObject.subscription.off()

     delete @decorationsByMarkerId[marker.id]
     delete @decorationMarkerChangedSubscriptions[marker.id]
     delete @decorationMarkerDestroyedSubscriptions[marker.id]
-
-  unsubscribeDecorationFromMarker: (marker, decoration) ->
-    subscriptionObjects = @decorationMarkerChangedSubscriptions[marker.id]
-    return unless @decorationMarkerChangedSubscriptions[marker.id]
-
-    for i in [subscriptionObjects.length - 1..0]
-      subscriptionObject = subscriptionObjects[i]
-      if subscriptionObject.decoration == decoration
-        subscriptionObject.subscription.off()
-        subscriptionObjects.splice(i, 1)
-
-    delete @decorationMarkerChangedSubscriptions[marker.id] if subscriptionObjects.length == 0

   # Retrieves a {DisplayBufferMarker} based on its id.
   #


rm about page console

rm about page console

--- assets/about.coffee
+++ assets/about.coffee
@@ -8,7 +8,6 @@
   $('.static--title--top').waypoint
     handler: (direction) ->
       id = $(this).attr 'id'
-      console.log 'id', id
       $(".static__menu a.is-active").removeClass 'is-active'
       $(".static__menu a[data-section=#{id}]").addClass 'is-active'


fix https://github.com/sagemathinc/smc/issues/1714 -- "course/main: CourseEditor.show_files -> set_focused_page is not a function" -- somebody changed the name of the function in the project actions, but didn't update in the course!

--- src/smc-webapp/course/main.cjsx
+++ src/smc-webapp/course/main.cjsx
@@ -1973,7 +1973,7 @@
         <SaveButton saving={@props.saving} unsaved={true} on_click={=>@props.redux.getActions(@props.name).save()}/>

     show_files: ->
-        @props.redux?.getProjectActions(@props.project_id).set_focused_page('project-file-listing')
+        @props.redux?.getProjectActions(@props.project_id).set_active_tab('files')

     render_files_button: ->
         <Button className='smc-small-only' style={float:'right', marginLeft:'15px'}


remove debugging code

remove debugging code

--- src/smc-webapp/smc-react.coffee
+++ src/smc-webapp/smc-react.coffee
@@ -11,9 +11,6 @@

 redux_lib = require('redux')
 {Provider, connect} = require('react-redux')
-
-# for testing for now
-smc.redux_lib = redux_lib

 misc = require('smc-util/misc')
 {defaults, required} = misc


revert change

revert change

--- src/synsetRepresentation.coffee
+++ src/synsetRepresentation.coffee
@@ -25,11 +25,17 @@

 class Vocabulary
   constructor: () ->
-    @dict = new HashTable()
+    @dict = new BinarySearchTree((a, b) ->
+      if (a < b)
+        return -1
+      if (a > b)
+        return 1
+      return 0
+    )
   add: (word) ->
     if not @dict.has(word)
       current_length = @dict.length
-      @dict.put(word, current_length)
+      @dict.set(word, current_length)
       return current_length
     else
       index = @dict.get(word)
@@ -42,6 +48,7 @@
       ret[value] = key
     )
     return ret
+

 getCorpusSynsets = (docs) ->
   if Array.isArray(docs) is false then docs = Array(docs)


impress is standard (for development)

impress is standard (for development)

--- src/javascript/app.coffee
+++ src/javascript/app.coffee
@@ -26,6 +26,6 @@
         templateUrl: 'partials/impress.html',
         controller: 'ImpressController'
       .otherwise
-        redirectTo: '/messages'
+        redirectTo: '/impress'
       return
   ]


Medium Editor is the only available component

--- src/js/faber/faber.coffee
+++ src/js/faber/faber.coffee
@@ -15,11 +15,5 @@
       #               and can not be a child of other block
       components: [
           new MediumEditorComponent()
-        ,
-          new Element1()
-        ,
-          name: 'Element 2',
-          type: 'element',
-          template: '/js/components/samples/element2.html'
       ]
     })


Don’t need this return

Don’t need this return

--- src/keymap-manager.coffee
+++ src/keymap-manager.coffee
@@ -527,7 +527,7 @@
       @simulateTextInput(event) if event.defaultPrevented and event.type is 'keydown'
       queuedKeystrokes = null

-    return {keystroke, exactMatch, partialMatches, queuedKeystrokes}
+    {keystroke, exactMatch, partialMatches, queuedKeystrokes}

   # Public: Translate a keydown event to a keystroke string.


- remove doOperation() method from Subkey

--- src/subkey.coffee
+++ src/subkey.coffee
@@ -253,44 +253,6 @@
       for k of vSubkeys
         vSubkeys[k].free()
       return
-    _doOperation: (aOperation, opts, cb) ->
-      if isFunction opts
-        cb = opts
-        opts = {}
-      else opts = {}  if opts is `undefined`
-      assignDeprecatedPrefixOption opts
-      vPath = if isString(opts.path) and opts.path.length then getPathArray(opts.path) else @_pathArray
-      that = @
-      if isArray(aOperation)
-        vType = "batch"
-        aOperation = aOperation.map((op) ->
-          separator: op.separator
-          key: op.key
-          value: op.value
-          path: resolvePathArray vPath, op.path
-          keyEncoding: op.keyEncoding # *
-          valueEncoding: op.valueEncoding # * (TODO: encodings on sublevel)
-          type: op.type
-        )
-        vInfo = [vType, aOperation]
-      else
-        vType = aOperation.type
-        vInfo = [vType, aOperation.key, aOperation.value]
-        aOperation = [
-          separator: opts.separator
-          path: vPath
-          key: aOperation.key
-          value: aOperation.value
-          type: aOperation.type
-        ]
-      if cb
-        aDbCore.batchAsync aOperation, @mergeOpts(opts), (err) ->
-          unless err
-            that.emit.apply that, vInfo
-            cb.call that, null
-          cb.call that, err  if err
-      else
-        aDbCore.batchSync aOperation, @mergeOpts(opts)
     ###
       put it self:
         put(cb)
@@ -323,7 +285,7 @@
         cb = value
         value = key
         key = "."
-      @_doOperation({key:key, value:value, type: "put"}, opts, cb)
+      if cb then putAsync key, value, opts, callback else putSync key, value, opts
     ###TODO: del itself would destroy itself?  see: the post hook itself in init method.
       del itself:
       del(cb)
@@ -351,7 +313,7 @@
       if isFunction(key) or arguments.length is 0
         cb = key
         key = @path() #use absolute key path to delete alias key itself
-      @_doOperation({key:key, type: "del"}, opts, cb)
+      if cb then delAsync key, opts, callback else delSync key, opts
     batchSync: (ops, opts) ->
       opts = @mergeOpts(opts)
       assignDeprecatedPrefixOption opts
@@ -366,7 +328,10 @@
       opts.path = getPathArray opts.path, @_pathArray
       aDbCore.batchAsync ops, opts, callback
     batch: (ops, opts, cb) ->
-      @_doOperation(ops, opts, cb)
+      if isFunction opts
+        cb = opts
+        opts = {}
+      if cb then batchAsync ops, opts, callback else batchSync ops, opts
     getSync: (key, opts) ->
       if isObject key
         opts = key


Disable pigments

Disable pigments

I have found pigments to be the source of terrible lag and freeze-ups
within Atom while working on a large project. See the GitHub issue:
<https://github.com/abe33/atom-pigments/issues/275>

--- atom/config.cson
+++ atom/config.cson
@@ -32,6 +32,7 @@
       "language-perl"
       "metrics"
       "open-on-github"
+      "pigments"
       "release-notes"
       "script"
       "timecop"


Correct position of AppleScript comment override

--- atom/config.cson
+++ atom/config.cson
@@ -172,6 +172,9 @@
     showOnStartup: false
   whitespace:
     ignoreWhitespaceOnCurrentLine: false
+".applescript.source":
+  editor:
+    commentStart: "--"
 ".basic.html.text":
   editor:
     nonWordCharacters: "/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?-"
@@ -212,6 +215,3 @@
 ".python.source":
   editor:
     preferredLineLength: 79
-".applescript.source":
-  editor:
-    commentStart: "--"


Force language-javascript to override language-babel

--- atom/config.cson
+++ atom/config.cson
@@ -24,6 +24,9 @@
     allowPendingPaneItems: false
     closeEmptyWindows: false
     customFileTypes:
+      "source.js": [
+        "js"
+      ]
       "text.pip.pip-conf": [
         "coveragerc"
       ]


Avoid preconnect operations

Avoid preconnect operations

--- src/sys/ad.coffee
+++ src/sys/ad.coffee
@@ -2,21 +2,22 @@
 # Подключение к Active Directory
 #

+info = dc = root = base =
 h = @h

-@info =
-info = new ActiveXObject "ADSystemInfo"
+@connect = ->
+  @info =
+  info = new ActiveXObject "ADSystemInfo"

-@dc =
-dc = info.DomainShortName
+  @dc =
+  dc = info.DomainShortName

-@rootDSE =
-root = GetObject "LDAP://rootDSE"
+  @rootDSE =
+  root = GetObject "LDAP://rootDSE"

-@base =
-base = root.Get 'rootDomainNamingContext'
+  @base =
+  base = root.Get 'rootDomainNamingContext'

-@connect = ->
   @h = h = new ActiveXObject "ADODB.Connection"
   h.Provider = "ADsDSOObject"
   h.Open "Active Directory Provider"


Update wrap guide settings

Update wrap guide settings

Now that the official version of Atom includes the change for the wrap
guide supporting scope names, I can update my wrap guide settings to the
version that I've wanted.

--- atom/config.cson
+++ atom/config.cson
@@ -87,7 +87,7 @@
       'column': -1
     }
     {
-      'pattern': '.md$'
-      'column': -1
+      'scope': 'text.git-commit'
+      'column': 72
     }
   ]


Poor logging.

Poor logging.

--- src/adapters/campfire.coffee
+++ src/adapters/campfire.coffee
@@ -42,8 +42,8 @@
       bot.Me (selfData) ->
         bot.User user, (err, selfdata) ->
           console.log selfdata
-          console.log selfdata.id
-          console.log selfdata.name
+          console.log selfdata.user.id
+          console.log selfdata.user.id

     bot.on "TextMessage", withAuthor (id, created, room, user, body, author) ->
       unless bot.info.id == author.id
@@ -56,8 +56,8 @@
     bot.on "LeaveMessage", withAuthor (id, created, room, user, body, author) ->
       unless bot.info.id == author.id
         self.receive new Robot.LeaveMessage(author)
-
-    bot.on "SoundMessage", withSelf (id, created, room, user , author) ->
+
+    bot.on "TimestampMessage", withSelf (id, created, room, user, body, author) ->
       console.log "Timestamp"
       console.log author.id
       console.log author.room


Free @currentPage reference in AnimatableRegion after page transition

This way the previous page and anything it references can be garbage
collected immediately when the page is out of the viewport.

--- src/animatable_region.coffee
+++ src/animatable_region.coffee
@@ -112,6 +112,7 @@
           if @currentPage
             if @currentPage.close then @currentPage.close()
             else @currentPage.remove()
+            @currentPage = null

           @back = undefined


Datasource likeParser dont fail if no header

--- src/client/datasource.coffee
+++ src/client/datasource.coffee
@@ -102,7 +102,7 @@
         transportDfd = @transport(@def)
         transportDfd.then (obj, headers) =>
           if _.isArray(obj)
-            @paginationLinks = parseLinkHeader(headers['Link'])
+            @paginationLinks = parseLinkHeader(headers['Link']) if headers?.Link
             dfd.resolve (new Backbone.Collection obj)
           else
             dfd.resolve (new Backbone.Model obj)


Add 'symbol' option to viewer

Add 'symbol' option to viewer

--- src/viewer.coffee
+++ src/viewer.coffee
@@ -34,6 +34,9 @@
 #       (This rectangle will be fit snuggly into the DOM node provided.)
 #
 #   You may provide...
+#     symbol: [string]
+#       Label of the symbol to be viewed. (Must be unique.) If missing, the
+#       currently edited symbol will be viewed.
 #     onLoad: [function]
 #       Callback to be run once the diagram is loaded (before any rendering),
 #       with `this` taking the value of the ApparatusViewer.
@@ -76,6 +79,18 @@
     if foundMultiple
       throw "Found multiple attributes with label #{label}"
     return new Attribute(attribute, this)
+
+  # Switches the viewer to the symbol with label `label`, provided this label is
+  # unique.
+  switchToSymbol: (label) ->
+    matchingSymbols =
+      @_project.createPanelElements.filter (symbol) -> symbol.label == label
+    if matchingSymbols.length == 1
+      @_project.setEditing(matchingSymbols[0])
+    else if matchingSymbols.length < 1
+      throw "Found no symbols with label #{label}"
+    else
+      throw "Found multiple symbols with label #{label}"

   # ADVANCED USAGE: Returns a reference to the internal `Project` object being
   # displayed in the viewer. You can read and write to this object, but beware:
@@ -122,6 +137,9 @@
     rect = @_element.getBoundingClientRect()
     @_project.editingElement.zoomViewMatrixToRegionOfInterest(
       regionOfInterest, rect.width, rect.height)
+
+    if @options.symbol?
+      @switchToSymbol(@options.symbol)

     @options.onLoad?.apply(this)


Remove unsaved registrations from collection when cancelling creation

--- app/assets/javascripts/admin/views/registrations/edit.js.coffee
+++ app/assets/javascripts/admin/views/registrations/edit.js.coffee
@@ -26,6 +26,8 @@

   cancel: (e) ->
     e.preventDefault()
+    if @model.isNew()
+      Augury.registrations.remove @model
     Backbone.history.navigate '/registrations', trigger: true

   saved: ->


Remove Model subclassing from PaneContainer

--- src/pane-container.coffee
+++ src/pane-container.coffee
@@ -6,19 +6,18 @@
 PaneContainerElement = require './pane-container-element'

 module.exports =
-class PaneContainer extends Model
+class PaneContainer
   serializationVersion: 1
   root: null
   stoppedChangingActivePaneItemDelay: 100
   stoppedChangingActivePaneItemTimeout: null

   constructor: (params) ->
-    super
-
     {@config, applicationDelegate, notificationManager, deserializerManager, @viewRegistry, @location} = params
     @emitter = new Emitter
     @subscriptions = new CompositeDisposable
     @itemRegistry = new ItemRegistry
+    @alive = true

     @setRoot(new Pane({container: this, @config, applicationDelegate, notificationManager, deserializerManager, @viewRegistry}))
     @setActivePane(@getRoot())
@@ -31,6 +30,17 @@

   getElement: ->
     @element ?= new PaneContainerElement().initialize(this, {views: @viewRegistry})
+
+  destroy: ->
+    @alive = false
+    @cancelStoppedChangingActivePaneItemTimeout()
+    pane.destroy() for pane in @getRoot().getPanes()
+    @subscriptions.dispose()
+    @emitter.dispose()
+
+  isAlive: -> @alive
+
+  isDestroyed: -> not @isAlive()

   serialize: (params) ->
     deserializer: 'PaneContainer'
@@ -206,13 +216,6 @@
   didDestroyPane: (event) ->
     @emitter.emit 'did-destroy-pane', event

-  # Called by Model superclass when destroyed
-  destroyed: ->
-    @cancelStoppedChangingActivePaneItemTimeout()
-    pane.destroy() for pane in @getRoot().getPanes()
-    @subscriptions.dispose()
-    @emitter.dispose()
-
   cancelStoppedChangingActivePaneItemTimeout: ->
     if @stoppedChangingActivePaneItemTimeout?
       clearTimeout(@stoppedChangingActivePaneItemTimeout)


Wrap item in array since it is a trigger call

--- src/pane-container.coffee
+++ src/pane-container.coffee
@@ -66,7 +66,7 @@
     panes[nextIndex].makeActive()

   itemDestroyed: (item) ->
-    @trigger 'item-destroyed', item
+    @trigger 'item-destroyed', [item]

   getRoot: ->
     @children().first().view()


simpler event binding syntax

simpler event binding syntax

as per @eamonnerbonne 's suggestion

--- js-library/app/js/util/scrollspeed.coffee
+++ js-library/app/js/util/scrollspeed.coffee
@@ -35,7 +35,7 @@
     else
       speeding_attention.loseAttention()

-$(window).scroll check_scrolling_speed
+$(window).on 'scroll', check_scrolling_speed

 # flag to ensure we only fire on state chaneg
 currently_fast_scrolling = false


Use cell images

Use cell images

--- src/client/views/grid.coffee
+++ src/client/views/grid.coffee
@@ -13,7 +13,7 @@
   ownerImage: ->
     owner = Meteor.users.findOne @ownerId
     return unless owner
-    owner.profile.image
+    owner.profile.cellImages[@y][@x].image

 Template.grid.events
   'click button': (event) ->


Allow `Spec.extend` to accept multiple arguments.

--- lib/Spec.coffee
+++ lib/Spec.coffee
@@ -32,12 +32,13 @@
   escape: (string) ->
     $('
').text(String(string)).html()

-  # Extends a single class with test methods
-  extend: (klass) ->
-    @_extended.push klass
-    $.extend klass, @ObjectExtensions
-    $.extend klass.prototype, @ObjectExtensions if klass.prototype
-
+  # Extends a one or more classes with test methods
+  extend: () ->
+    for klass in arguments
+      @_extended.push klass
+      $.extend klass, @ObjectExtensions
+      $.extend klass.prototype, @ObjectExtensions if klass.prototype
+
   # Fails test, with an error message
   fail: (message, location) ->
     @passed = false
@@ -102,16 +103,8 @@
       when 'terminal'
         $('body').append('

')

-    @extend SpecObject
-    @extend Array
-    @extend Boolean
-    @extend Date
-    @extend Function
-    @extend Number
-    @extend RegExp
-    @extend String
-    @extend Element
-    @extend jQuery
+    @extend Array, Boolean, Date, Element, Function, jQuery, Number, RegExp,
+      SpecObject, String

   # Returns an HTML representation of any kind of object
   inspect: (object) ->


little example of use of static method

little example of use of static method

--- lib/User.coffee
+++ lib/User.coffee
@@ -11,10 +11,8 @@
   # -- Static Class Method ------------------
   # defined with '@'
   # Public static method
-  @say: -> console.log "I'm a user model, u know ?"
-  # Private static method
-  # Use case for represent a constant value
-  @VARIABLE: 3
+  @NAME: 'User'
+  @say: -> console.log "I'm a #{@NAME} model, u know ?"

   # -- Private Class Method -----------------
   # defined whit '_'


fixed org members retrieval

fixed org members retrieval

--- app/assets/javascripts/mno_enterprise/angular/directives/dashboard/organization/members.js.coffee
+++ app/assets/javascripts/mno_enterprise/angular/directives/dashboard/organization/members.js.coffee
@@ -242,7 +242,7 @@
     #====================================
     refresh = ->
       $q.all([DhbOrganizationSvc.load(),DhbTeamSvc.load()]).then (values)->
-        $scope.initialize(values[0].members,values[1])
+        $scope.initialize(values[0].organization.members,values[1])

     contextObj = ->
       { orgId: DhbOrganizationSvc.getId(), teams: DhbTeamSvc.data }


play track if track available in demo (closes #4)

--- demo/coffee/app.coffee
+++ demo/coffee/app.coffee
@@ -98,5 +98,6 @@
   replace: true,
   link: ($scope, $element, $attrs) ->
     $scope.$watch 'track', (track) ->
-      $element.attr('src', track.stream_url + "?client_id=8399f2e0577e0acb4eee4d65d6c6cce6")
-      $element.get(0).play()
+      if track
+        $element.attr('src', track.stream_url + "?client_id=8399f2e0577e0acb4eee4d65d6c6cce6")
+        $element.get(0).play()


:bug: fix no parameter constructor has no ()

--- src/renderer/components/signatures/doc-signatures-component.coffee
+++ src/renderer/components/signatures/doc-signatures-component.coffee
@@ -24,7 +24,7 @@
         elm.push <DocSignaturesNameComponent base={@props.signature} emphasisStyle={@props.emphasisStyle} name={@props.name} />
         # (name?: type.name<typeArgument, ...>, ...)
         params = @props.signature.parameters
-        params = [] if !params? && (@props.signature.kindString == 'Get signature' || @props.signature.kindString == 'Set signature' || @props.signature.kindString == 'Call signature')
+        params = [] if !params? && (@props.signature.kindString == 'Get signature' || @props.signature.kindString == 'Set signature' || @props.signature.kindString == 'Call signature' || @props.signature.kindString == 'Constructor signature')
         if params?
           elm.push <DocSignaturesParametersComponent parameters={params} emphasisStyle={@props.emphasisStyle} />
         # :


ambush.coffe using unless operator

ambush.coffe using unless operator

--- src/scripts/ambush.coffee
+++ src/scripts/ambush.coffee
@@ -35,8 +35,7 @@
       msg.send "#{msg.match[1]}? Never heard of 'em"

   robot.hear /./i, (msg) ->
-    if (!robot.brain.data.ambushes)
-      return
+    return unless robot.brain.data.ambuses?
     if (ambushes = robot.brain.data.ambushes[msg.message.user.name])
       msg.send "Hey, " + msg.message.user.name + ", while you were out:"
       for ambush in ambushes


just added description

just added description

--- src/scripts/locpix.coffee
+++ src/scripts/locpix.coffee
@@ -8,7 +8,7 @@
 #   None
 #
 # Commands:
-#   hubot locpix me <query>
+#   hubot locpix me <query> - Search the Library of Congress image archives
 #
 # Author:
 #   pj4533


Using array comprehension to make columns array

--- src/services/table.coffee
+++ src/services/table.coffee
@@ -52,9 +52,7 @@
 angular.module("Mac").factory "dynamicColumnsFunction", ->
   (models) ->
     first   = models[0]
-    columns = []
-    for key, model of first
-        columns.push key
+    columns = (key for key, model of first)
     @set(columns)

 angular.module("Mac").factory "ColumnsController", [