Fixed paging.

Fixed paging.

--- lib/handlers.coffee
+++ lib/handlers.coffee
@@ -29,7 +29,7 @@
   handler: (api, components)->
     db = api.crawly.getRedis()
     page = if api.purl.query['page'] then api.purl.query['page'] else 0
-    db.zrevrange 'uri:score', page*20, 20, 'WITHSCORES', (err, result)->
+    db.zrevrange 'uri:score', page*20, page*20+19, 'WITHSCORES', (err, result)->
       if not err
         api.result result
       else


Only pass `?type=` parameter through for Entries and Packages

--- compiler/ContentAPI.coffee
+++ compiler/ContentAPI.coffee
@@ -281,6 +281,12 @@

         deferred_result = W.defer()

+        # Other types don't have the `type` property, so this needs to be
+        # removed from the query for the filtering to work correctly.
+        url = ENDPOINTS[query.type]
+        unless query.type in [ENTRY, PACKAGE]
+            delete query.type
+
         LIMIT = 10
         query._limit ?= LIMIT
         query._offset ?= 0
@@ -294,7 +300,7 @@
                 cb?(_results)
             else
                 @_sendRequest
-                    url         : ENDPOINTS[query.type]
+                    url         : url
                     query       : query
                     callback    : (_results) ->
                         results.push(_results...)


Allow object to be specified as the controller in a route match statement (e.g. `match 'foo', controller: FooController, action: 'show'`).

--- src/chaplin/dispatcher.coffee
+++ src/chaplin/dispatcher.coffee
@@ -77,6 +77,8 @@
   # The default implementation uses require() from a AMD module loader
   # like RequireJS to fetch the constructor.
   loadController: (name, handler) ->
+    return handler(name) if _.isObject name
+
     fileName = name + @settings.controllerSuffix
     moduleName = @settings.controllerPath + fileName
     if define?.amd


sync: fixes

sync: fixes

--- src/sync.coffee
+++ src/sync.coffee
@@ -100,7 +100,7 @@
     operation: 'retrieve-node-subscriptions'

     run: (t, cb) ->
-        super (err) =>
+        super t, (err) =>
             if err
                 return cb err

@@ -108,7 +108,7 @@
                 cb.apply @, arguments
             # After all subscriptions have synced, check if any local
             # subscriptions are left:
-            t.getNodeListeners @node, (err, listeners) ->
+            t.getNodeListeners @node, (err, listeners) =>
                 if err
                     logger.error "Cannot get node listeners: #{err.stack or err}"

@@ -131,7 +131,7 @@
             # SubscriptionsSynchronization which may have purged the
             # node.
             t.validateNode @node
-        , (cb2) ->
+        , (cb2) =>
             t.resetAffiliations(@node, cb2)
         ], cb


see previous 3 commits fix #102

see previous 3 commits
fix #102

--- src/client/common_stat.coffee
+++ src/client/common_stat.coffee
@@ -14,7 +14,7 @@
     filterRow: '',
     even: '',
     odd: ''
-  });
+  })

   $.tablesorter.addWidget({
     id: 'indexFirstColumn'


trim output when error occurs

trim output when error occurs

--- services/spelling/app/coffee/ASpellWorker.coffee
+++ services/spelling/app/coffee/ASpellWorker.coffee
@@ -17,12 +17,12 @@
 			metrics.inc "aspellWorker-exit-" + @language
 		@pipe.on 'error', (err) =>
 			@state = 'error' unless @state == 'killed'
-			logger.log process: @pipe.pid, error: err, stdout: output, stderr: error, lang: @language, "aspell worker error"
+			logger.log process: @pipe.pid, error: err, stdout: output.slice(-1024), stderr: error.slice(-1024), lang: @language, "aspell worker error"
 			if @callback?
 				@callback err, []
 		@pipe.stdin.on 'error', (err) =>
 			@state = 'error' unless @state == 'killed'
-			logger.log process: @pipe.pid, error: err, stdout: output, stderr: error, lang: @language, "aspell worker error on stdin"
+			logger.log process: @pipe.pid, error: err, stdout: output.slice(-1024), stderr: error.slice(-1024), lang: @language, "aspell worker error on stdin"
 			if @callback?
 				@callback err, []


experimented with quicker polling times

experimented with quicker polling times

--- lib/mobwrite.coffee
+++ lib/mobwrite.coffee
@@ -182,6 +182,9 @@
           <script>
               //mobwrite.syncGateway = location.protocol + '//' + location.host + "/__mobwrite__/";
               mobwrite.debug = true;
+              mobwrite.syncInterval = 250
+              mobwrite.minSyncInterval = 250
+              mobwrite.maxSyncInterval = 1250
               mobwrite.share('mobwrite-form');
           </script>
       </body>


Added date ranges to FakeClock that identify when there is sensor data in contiguous 5 minute blocks


Update AdminEditOrderCycleCtrl unit tests

--- spec/javascripts/unit/order_cycle_spec.js.coffee
+++ spec/javascripts/unit/order_cycle_spec.js.coffee
@@ -9,7 +9,7 @@
     EnterpriseFee = null

     beforeEach ->
-      scope = {}
+      scope = { order_cycle_form: { $dirty: false}}
       event =
         preventDefault: jasmine.createSpy('preventDefault')
       OrderCycle =
@@ -169,7 +169,9 @@
     EnterpriseFee = null

     beforeEach ->
-      scope = {}
+      scope =
+        order_cycle_form: jasmine.createSpyObj('order_cycle_form', ['$dirty', '$setPristine'])
+        $watch: jasmine.createSpy('$watch')
       event =
         preventDefault: jasmine.createSpy('preventDefault')
       location =
@@ -292,6 +294,7 @@
       scope.removeExchange(event, 'exchange')
       expect(event.preventDefault).toHaveBeenCalled()
       expect(OrderCycle.removeExchange).toHaveBeenCalledWith('exchange')
+      expect(scope.order_cycle_form.$dirty).toEqual true

     it 'Adds coordinator fees', ->
       scope.addCoordinatorFee(event)
@@ -320,6 +323,7 @@
     it 'Submits the order cycle via OrderCycle update', ->
       scope.submit('/admin/order_cycles')
       expect(OrderCycle.update).toHaveBeenCalledWith('/admin/order_cycles')
+      expect(scope.order_cycle_form.$setPristine.calls.length).toEqual 1

 describe 'OrderCycle services', ->


:fire: unused

:fire: unused

--- lib/operator.coffee
+++ lib/operator.coffee
@@ -411,9 +411,6 @@

   getRangeTranslationSpec: ->
     [[-1, 0], [0, 0]]
-
-  setBufferRange: (s, range) ->
-    swrap(s).setBufferRange(range, {preserveFolds: true})

   mutate: (s, setPoint) ->
     return unless @isMovable(s)


See #68. Resolve Unsafe-Eval error with CoffeeScript Preview

--- lib/renderer.coffee
+++ lib/renderer.coffee
@@ -44,16 +44,18 @@
   grammars:
     'CoffeeScript':
       render: (text, filepath, cb) ->
-        coffeescript = require 'coffee-script'
-        result = coffeescript.compile text
-        cb null, result
+        allowUnsafeEval ->
+          coffeescript = require 'coffee-script'
+          result = coffeescript.compile text
+          cb null, result
       exts: /\.(coffee)$/i
       lang: -> 'js'
     'CoffeeScript (Literate)':
       render: (text, filepath, cb) ->
-        coffeescript = require 'coffee-script'
-        result = coffeescript.compile text, literate: true
-        cb null, result
+        allowUnsafeEval ->
+          coffeescript = require 'coffee-script'
+          result = coffeescript.compile text, literate: true
+          cb null, result
       exts: /\.(litcoffee)$/i
       lang: -> 'js'
     'TypeScript':


Remove a line continuation test

Remove a line continuation test

i'm not sure if it's still relevant

--- spec/powershell-spec.coffee
+++ spec/powershell-spec.coffee
@@ -382,12 +382,7 @@
     it "considers a backtick followed by a newline as a line continuation", ->
       {tokens} = grammar.tokenizeLine("`\n")
       expect(tokens[0].value).toEqual("`")
-      expect(tokens[0]).toHaveScopes ["punctuation.separator.continuation.line.powershell"]
-
-    it "considers a backtick followed by whitespace and a newline as a line continuation", ->
-      {tokens} = grammar.tokenizeLine("`  \n")
-      expect(tokens[0].value).toEqual("`")
-      expect(tokens[0]).toHaveScopes ["punctuation.separator.continuation.line.powershell"]
+      expect(tokens[0]).toHaveScopes ["source.powershell", "keyword.operator.other.powershell"]

   describe "indentation", ->
     editor = null


jenkins-notifier.coffee: Encode project urls with whitespaces #2

Signed-off-by: Philipp Beckmann <6ebf6b6479c01d1560656435e24a71ef1da2ad7a@pabnet.de>

--- src/scripts/jenkins-notifier.coffee
+++ src/scripts/jenkins-notifier.coffee
@@ -49,7 +49,7 @@
           if data.name in @failing
             index = @failing.indexOf data.name
             @failing.splice index, 1 if index isnt -1
-            robot.send user, "BUILD RESTORED: #{data.name} ##{data.build.number} (#{data.build.full_url})"
+            robot.send user, "BUILD RESTORED: #{data.name} ##{data.build.number} (#{encodeURI(data.build.full_url)})"

     catch error
       console.log "jenkins-notify error: #{error}. Data: #{req.body}"


added portuguese to the available locales

--- src/core/locale/locale.coffee
+++ src/core/locale/locale.coffee
@@ -4,5 +4,6 @@
   fr_FR: require "./languages/fr_FR.js"
   mk_MK: require "./languages/mk_MK.js"
   pt_BR: require "./languages/pt_BR.js"
+  pt_PT: require "./languages/pt_PT.js"
   ru_RU: require "./languages/ru_RU.js"
   zh_CN: require "./languages/zh_CN.js"


Don't run kontrol on vagrant

Don't run kontrol on vagrant

--- config/main.vagrant.coffee
+++ config/main.vagrant.coffee
@@ -40,7 +40,7 @@
     port        : 7474
   runNeo4jFeeder: yes
   runGoBroker   : yes
-  runKontrol    : yes
+  runKontrol    : no
   runRerouting  : yes
   runUserPresence: yes
   runPersistence: yes


use v_select instead of selecting in a loop

--- static/coffee/renderers/glyph/glyph.coffee
+++ static/coffee/renderers/glyph/glyph.coffee
@@ -59,14 +59,14 @@

     if position == 'center'
       halfspan = (d / 2 for d in span)
-      ptc = (@glyph_props.select(pt, x) for x in data)
+      ptc = @glyph_props.v_select(pt, data)
       if pt_units == 'screen'
         ptc = mapper.v_map_from_target(ptc)
       pt0 = (ptc[i] - halfspan[i] for i in [0..ptc.length-1])
       pt1 = (ptc[i] + halfspan[i] for i in [0..ptc.length-1])

     else
-      pt0 = (@glyph_props.select(pt, x) for x in data)
+      pt0 = @glyph_props.v_select(pt, data)
       if pt_units == 'screen'
         pt0 = mapper.v_map_from_target(pt0)
       pt1 = (pt0[i] + span[i] for i in [0..pt0.length-1])


Log a message when the web api produces a 404 response.

--- services/track-changes/app/coffee/WebApiManager.coffee
+++ services/track-changes/app/coffee/WebApiManager.coffee
@@ -14,6 +14,7 @@
 			if error?
 				return callback(error)
 			if res.statusCode == 404
+				logger.log url: url, "got 404 from web api"
 				return callback null, null
 			if res.statusCode >= 200 and res.statusCode < 300
 				return callback null, body


Fix for crash when the message has a null user.  Problem experienced with HipChat adapter.

/opt/hubot/node_modules/hubot-scripts/src/scripts/conversation.coffee:13
      if (robot.eatListeners[message.user.id] != null) {
                                         ^
TypeError: Cannot read property 'id' of undefined
    at Robot.receive (/opt/hubot/node_modules/hubot-scripts/src/scripts/conversation.coffee:13:42)
    at Robot.origReceive (/opt/hubot/node_modules/hubot/src/robot.coffee:120:21)
    at Robot.receive (/opt/hubot/node_modules/hubot-scripts/src/scripts/conversation.coffee:21:20)
    at HipChat.receive (/opt/hubot/node_modules/hubot/src/adapter.coffee:44:25)
    at Bot.<anonymous> (/opt/hubot/node_modules/hubot-hipchat/src/hipchat.coffee:127:21)
    at Bot.emit (events.js:77:17)
    at Bot.<anonymous> (/opt/hubot/node_modules/hubot-hipchat/node_modules/wobot/lib/bot.js:108:12)
    at Client.<anonymous> (native)
    at Client.emit (events.js:67:17)
    at Client.onRawStanza (/opt/hubot/node_modules/hubot-hipchat/node_modules/wobot/node_modules/node-xmpp/lib/xmpp/client.js:159:14)

--- src/scripts/conversation.coffee
+++ src/scripts/conversation.coffee
@@ -17,7 +17,7 @@
   # Change default receive command, addind processing of eatListeners
   robot.origReceive = robot.receive
   robot.receive = (message) ->
-    if robot.eatListeners[message.user.id]?
+    if message.user? and robot.eatListeners[message.user.id]?
       lst = robot.eatListeners[message.user.id]
       delete robot.eatListeners[message.user.id]


Fixing typo

Fixing typo

--- source/core/eventemitter.coffee
+++ source/core/eventemitter.coffee
@@ -66,7 +66,7 @@
   bindList: (el, name, events) ->
     #@TODO: Better if use internal @name
     for event in events
-      el.bind event, @["#{name.toLowerCase()}#{Atoms.Core.Helper.name(event)}"]
+      el.bind event, @["#{name.toLowerCase()}#{Atoms.Core.Helper.className(event)}"]

   # Private Methods
   _getNameOfEvent: (type="", className, event) ->


dsync -- works as long as I don't simulate random packet loss.

--- salvus/dsync.coffee
+++ salvus/dsync.coffee
@@ -39,6 +39,15 @@
         @last_version_received = 0
         @edit_stack            = []

+    status: () =>
+        return {'id':@id, 'live':@live, 'shadow':@shadow, 'shadow_version':@shadow_version, 'edit_stack':@edit_stack}
+
+    restart: (reason) =>
+        console.log("*********************************************************")
+        console.log("* THINGS WENT TO HELL. -- #{reason} --  HAVE TO RESTART!!!!! *")
+        console.log("*********************************************************")
+        throw("dang")
+
     # Copy a document; strings are immutable and the default, so we
     # just return the object.
     _copy: (doc) =>
@@ -52,8 +61,9 @@
     _apply_edits: (edits, doc) =>
         return dmp.patch_apply(edits, doc)[0]

-    status: () =>
-        return {'id':@id, 'live':@live, 'shadow':@shadow, 'shadow_version':@shadow_version, 'edit_stack':@edit_stack}
+    # Return a checksum of a document
+    _checksum: (doc) =>
+        return doc.length

     # Connect this client to the other end of the connection, the "server".
     connect: (server) =>
@@ -66,43 +76,60 @@

         if edits.edits.length > 0
             edits.shadow_version = @shadow_version
-            console.log("#{@id} -- push_edits -- #{misc.to_json(edits)}")
+            edits.shadow_checksum = @_checksum(@shadow)
+            # console.log("#{@id} -- push_edits -- #{misc.to_json(edits)}")
             @edit_stack.push(edits)
+            console.log("#{@id} -- shadow changes: '#{@shadow}' --> '#{@live}'  (version #{@shadow_version+1})")
             @shadow = @_copy(@live)
             @shadow_version += 1
         else
-            console.log("#{@id} -- push_edits -- (nothing new)")
+            # console.log("#{@id} -- push_edits -- (nothing new)")

-        # Push any remaining edits from the stack, *AND* report the last version received (also very important).
-        @server.recv_edits(@edit_stack, @last_version_received, cb)
+        # Push any remaining edits from the stack, *AND* report the last version we have received so far.
+        @server.recv_edits @edit_stack, @last_version_received, cb

     # Receive and process the edits from the other end of the sync connection.
     recv_edits: (edit_stack, last_version_ack, cb) =>
-        console.log("#{@id} -- recv_edits -- #{misc.to_json(edit_stack)}")
+
+        #console.log("#{@id} -- recv_edits -- #{misc.to_json(edit_stack)}")
         # Keep only edits that we still need to send.
         @edit_stack = (edits for edits in @edit_stack when edits.shadow_version > last_version_ack)
-        console.log("#{@id} -- remaining queued edits for next time: ", @edit_stack)
+
+        #console.log("#{@id} -- remaining queued edits for next time: ", @edit_stack)
         # process the incoming edits
         for edits in edit_stack
-            console.log("#{@id} -- must match: server shadow version = #{@shadow_version} and edit shadow version #{edits.shadow_version}")
-            @last_version_received = edits.shadow_version
-            @shadow = @_apply_edits(edits.edits, @shadow)
-            @shadow_version += 1
-            @live = @_apply_edits(edits.edits, @live)
-            @backup_shadow = @_copy(@shadow)
+            console.log("#{@id} -- our shadow version = #{@shadow_version} and client shadow version #{edits.shadow_version}")
+            # If edits.shadow_version does not equal @shadow_version, then there was a packet duplication or loss.
+            if edits.shadow_version < @shadow_version
+                console.log("Duplicate Packet: we have no interest in edits we have already processed.")
+                continue
+            else if edits.shadow_version > @shadow_version
+                @restart('shadow_version out of sync')
+            else if edits.shadow_checksum != @_checksum(@shadow)
+                # Data corruption in memory or network -- there should be no other way for this to happen.
+                # In this case, we have to just restart everything from scratch.
+                console.log("#{@id}: shadow = #{@shadow}, version = #{@shadow_version}")
+                @restart("checksum (edit_stack=#{misc.to_json(edit_stack)})")
+            else
+                # Everything looks golden.
+                @last_version_received = edits.shadow_version
+                console.log("#{@id} -- shadow changes: '#{@shadow}' --> '#{@_apply_edits(edits.edits, @shadow)}' (version #{@shadow_version+1})")
+                @shadow = @_apply_edits(edits.edits, @shadow)
+                @shadow_version += 1
+                @live = @_apply_edits(edits.edits, @live)
+                @backup_shadow = @_copy(@shadow)

         cb?()

-
 exports.test1 = () ->
-    client = new DSync(doc:"cat", id:"client")
-    server = new DSync(doc:"cat", id:"server")
+    client = new DSync(doc:"sage", id:"client")
+    server = new DSync(doc:"sage", id:"server")
     client.connect(server)
     server.connect(client)

-    client.live = "cats"
-    server.live = "my\ncat"
+    client.live = "sage"
+    server.live = "my\nsage"
     status = () ->
         console.log("------------------------")
         console.log(misc.to_json(client.status()))
@@ -125,8 +152,14 @@
     client.live = 'bar1\n' + client.live
     server.live = 'bar2\n'
     status()
-    client.push_edits()
-    server.push_edits()
+    while client.live != server.live
+        try
+            client.push_edits()
+            server.push_edits()
+            status()
+        catch e
+            status()
+            break
     status()

 exports.DSync = DSync


fix https://github.com/sagemathinc/smc/issues/1786 -- traceback involving course

--- src/smc-webapp/course/main.cjsx
+++ src/smc-webapp/course/main.cjsx
@@ -1445,7 +1445,7 @@
                 student_id = student.get('student_id')
                 status_map[student_id] = {time: misc.mswalltime()}
                 if err
-                    x[student_id].error = err
+                    status_map[student_id].error = err
                 obj.status = status_map
                 @_set(obj)


fix bug in editor_chat from pull request; make easier to read

--- src/smc-webapp/editor_chat.cjsx
+++ src/smc-webapp/editor_chat.cjsx
@@ -146,9 +146,9 @@
     content_column: ->
         value = @props.message.get('payload')?.get('content')
         if @sender_is_viewer()
-            color = 'info'
+            color = '#f5f5f5'
         else
-            color = ''
+            color = '#fff'
         # just for fun.
         value = value.replace(/:-\)/g, "☺")
                      .replace(/:-\(/g, "☹")
@@ -163,7 +163,7 @@
         <Col key={1} xs={8}>
             <Panel style={wordWrap:"break-word"}>
                 <ListGroup fill>
-                    <ListGroupItem bsStyle={color}>
+                    <ListGroupItem style={background:color}>
                         <Markdown value={value} project_id={@props.project_id} file_path={@props.file_path} />
                     </ListGroupItem>
                     {@get_timeago()}


go-webserver: add config to sandbox

go-webserver: add config to sandbox

--- config/main.sandbox.coffee
+++ config/main.sandbox.coffee
@@ -86,6 +86,7 @@

     # -- WORKER CONFIGURATION -- #

+    gowebserver                    : {port          : 6500}
     webserver                      : {port          : 3000                        , useCacheHeader: no                      , kitePort          : 8860 }
     authWorker                     : {login         : "#{rabbitmq.login}"         , queueName : socialQueueName+'auth'      , authExchange      : "auth"                                  , authAllExchange : "authAll"}
     mq                             : mq


fix (AppSettings) add configuration.extra_response_headers to the cloud json

--- src/steroids/AppSettings.coffee
+++ src/steroids/AppSettings.coffee
@@ -10,7 +10,6 @@
     "initialView"
     "tabs"
     "rootView"
-    "extra_response_headers"
   ]

   constructor: ->
@@ -24,6 +23,9 @@
     for whitelisted_keys in WHITELISTED_SETTING_KEYS
       settingsObject[whitelisted_keys] = tempSettingsObject[whitelisted_keys]

+    settingsObject.configuration = settingsObject.configuration || {}
+    settingsObject.configuration.extra_response_headers = tempSettingsObject.configuration?.extra_response_headers || {}
+
     JSON.stringify settingsObject, null, 2

   createJSONFile: ->


Ensure attributes are provide for branch validation

_validate defaults to using this.attributes which does not reflect
the branch's `children` collection.

--- src/coffee/cilantro/models/context/nodes/branch.coffee
+++ src/coffee/cilantro/models/context/nodes/branch.coffee
@@ -79,6 +79,13 @@
                 attrs.children.push child.toJSON()
             return attrs

+        # Validate base on the attributes prepared by toJSON. The internal
+        # attributes will likely be out of date especially for branch nodes.
+        _validate: (attrs, options) ->
+            if not attrs or _.isEmpty(attrs)
+                attrs = @toJSON(options)
+            super(attrs, options)
+
         # Branch nodes must of the type 'and' or 'or'
         validate: (attrs, options) ->
             if not (attrs.type is 'and' or attrs.type is 'or')


Remove assertion for impossible situation

We were testing the behavior of returning the active pane by removing
the active class from its view. But "active" is a model-level concept
now, so this assertion makes no sense.

--- spec/pane-container-spec.coffee
+++ spec/pane-container-spec.coffee
@@ -71,10 +71,6 @@
       expect(container.getFocusedPane()).toBe pane3
       expect(container.getActivePane()).toBe pane3

-      # returns the first pane if none have been set to active
-      container.find('.pane.active').removeClass('active')
-      expect(container.getActivePane()).toBe pane1
-
   describe ".eachPane(callback)", ->
     it "runs the callback with all current and future panes until the subscription is cancelled", ->
       panes = []


Fix pesky workspace view serialization spec

This isn't beautiful, but this whole approach is slated to be replaced
in the recent future.

--- spec/workspace-view-spec.coffee
+++ spec/workspace-view-spec.coffee
@@ -19,8 +19,10 @@
     viewState = null

     simulateReload = ->
-      atom.unloadEditorWindow()
-      atom.deserializeEditorWindow()
+      state = atom.workspaceView.serialize().replicate()
+      atom.workspaceView.remove()
+      atom.workspaceView = WorkspaceView.deserialize(state)
+      atom.workspaceView.attachToDom()

     describe "when the serialized WorkspaceView has an unsaved buffer", ->
       it "constructs the view with the same panes", ->
@@ -29,12 +31,12 @@
         editor1 = atom.workspaceView.getActiveView()
         buffer = editor1.getBuffer()
         editor1.splitRight()
-        expect(atom.workspaceView.getActiveView()).toBe atom.workspaceView.getEditorViews()[2]
+        expect(atom.workspaceView.getActivePane()).toBe atom.workspaceView.getPanes()[1]

         simulateReload()

         expect(atom.workspaceView.getEditorViews().length).toBe 2
-        expect(atom.workspaceView.getActiveView()).toBe atom.workspaceView.getEditorViews()[1]
+        expect(atom.workspaceView.getActivePane()).toBe atom.workspaceView.getPanes()[1]
         expect(atom.workspaceView.title).toBe "untitled - #{atom.project.getPath()}"

     describe "when there are open editors", ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/game_state_cameras.coffee
+++ src/game_state_cameras.coffee
@@ -2,7 +2,7 @@
   cameras = [Camera()]

   self.bind 'afterUpdate', ->
-    self.cameras().invoke 'trigger', "a"
+    self.cameras().invoke 'trigger',
       camera.trigger('afterUpdate')

   self.bind 'draw', (canvas) ->


update control_group and edge

update control_group and edge

--- src/desktop/components/split_test/running_tests.coffee
+++ src/desktop/components/split_test/running_tests.coffee
@@ -51,7 +51,7 @@
       'control'
       'experiment'
     ]
-    control_group: 0
-    edge: 1
+    control_group: 'control'
+    edge: 'experiment'
     weighting: 'equal'
 }


BUG-FIX Collection helper fixes after refactoring

--- src/helpers/collection.coffee
+++ src/helpers/collection.coffee
@@ -41,6 +41,7 @@
   addItem: (model)=>
     @items.push item = new Item(
       template: @template
+      externalTemplate: @scope.collectionView
       parent: @parent
       model: model
       itemName: @scope.collectionItem
@@ -69,6 +70,8 @@

   constructor: (opts)->
     super
+
+    @model = opts.model

     # Set own property
     Object.defineProperty @scope, opts.itemName,
@@ -80,6 +83,6 @@
     # For iterating number in view
     @scope._index = opts.index

-    unless @templateBody
+    if opts.externalTemplate
       @listenTo @scope[opts.itemName], 'change', =>
         @$el.replaceWith @render().$el


handle onClick events

handle onClick events

Former-commit-id: 17c5e46b33090d6b59aef5df55d6441e65353efe [formerly 50908255f7a65a88391866055892699e5a4f2f2e]
Former-commit-id: 0e2a108b777859f38ceb353fc758aadebe0ced5f

--- src/views/CanvasSeqBlock.coffee
+++ src/views/CanvasSeqBlock.coffee
@@ -3,6 +3,8 @@
 TaylorColors = require "./color/taylor"
 ZappoColors = require "./color/zappo"
 HydroColors = require "./color/hydrophobicity"
+mouse = require "../utils/mouse"
+_ = require "underscore"

 module.exports = pluginator.extend

@@ -23,6 +25,21 @@

     #@el.setAttribute 'width', 300
     #@el.style.width = 300
+
+    @manageEvents()
+
+  manageEvents: ->
+    events = {}
+    if @g.config.get "registerMouseClicks"
+      events.click = "_onclick"
+    if @g.config.get "registerMouseHover"
+      events.mousein = "_onmousein"
+      events.mouseout = "_onmouseout"
+    @delegateEvents events
+
+    # listen for changes
+    @listenTo @g.config, "change:registerMouseHover", @manageEvents
+    @listenTo @g.config, "change:registerMouseClick", @manageEvents

   draw: ->
     @removeViews()
@@ -50,9 +67,15 @@
     @ctx.font="14px Courier New"
     #@ctx.font="14px Lucida Console"

+    x = - @g.zoomer.get "_alignmentScrollLeft"
+    # skip unneeded blocks at the beginning
+    start = Math.max 0, - Math.ceil(x / rectWidth)
     x = 0
-    x = - @g.zoomer.get "_alignmentScrollLeft"
-    for j in [0.. seq.length - 1] by 1
+
+    selection = @_getSelection data.model
+    console.log selection
+
+    for j in [start.. seq.length - 1] by 1
       c = seq[j]
       c = c.toUpperCase()
       color = @color[c]
@@ -60,11 +83,32 @@
         @ctx.fillStyle = "#" + color
         @ctx.fillRect x,y,rectWidth,rectHeight
         @ctx.strokeText c,x + 3,y + 12,rectWidth
+
+      # check for selection
+      if selection.indexOf(j) >= 0
+        @ctx.lineWidth = 2
+        @ctx.fillStyle = "red"
+        @ctx.strokeRect x,y,rectWidth,rectHeight
+        @ctx.lineWidth = 1
+
       x = x + rectWidth

       # out of viewport - stop
       if x > @el.width
         break
+
+  _onclick: (e) ->
+    coords = mouse.getMouseCoords e
+    rectWidth = @g.zoomer.get "columnWidth"
+    rectHeight = @g.zoomer.get "rowHeight"
+    coords[0] += Math.floor(@g.zoomer.get("_alignmentScrollLeft") / rectWidth) * rectWidth
+    coords[1] += Math.floor(@g.zoomer.get("_alignmentScrollTop") / rectHeight) * rectHeight
+    x = Math.floor(coords[0] / rectWidth )
+    y = Math.floor(coords[1] / rectHeight)
+    seqId = @model.at(y).get "id"
+    @g.trigger "residue:click", {seqId:seqId, rowPos: x, evt:e}
+    console.log x,y
+    @render()

   render: ->

@@ -85,6 +129,26 @@
      paddingLeft += @g.zoomer.get "metaWidth" if @g.vis.get "metacell"
      return paddingLeft

+  # TODO: should I be moved to the selection manager?
+  # returns an array with the currently selected residues
+  # e.g. [0,3] = pos 0 and 3 are selected
+  _getSelection: (model) ->
+    maxLen = model.get("seq").length
+    selection = []
+    sels = @g.selcol.getSelForRow model.get "id"
+    rows = _.find sels, (el) -> el.get("type") is "row"
+    if rows?
+      # full match
+      for n in [0..maxLen - 1] by 1
+        selection.push n
+    else if sels.length > 0
+      for sel in sels
+        for n in [sel.get("xStart")..sel.get("xEnd")] by 1
+          selection.push n
+
+    return selection
+
+
   _adjustWidth: ->
     if @el.parentNode?
       parentWidth = @el.parentNode.offsetWidth


now looks for files with these extensions to include in each namespace: js,coffee,caffeine,caf In the future, this should be configurable.

--- src/Generation/Generator.coffee
+++ src/Generation/Generator.coffee
@@ -143,7 +143,7 @@

   generate: ->
     @log "\nscanning root: #{@root.yellow}" if @verbose
-    globPromise "#{@root}/**/*.coffee", dot: true
+    globPromise "#{@root}/**/*.{js,coffee,caffeine,caf}", dot: true
     .then (files) =>
       if files.length == 0
         error = "no .coffee files found"


making error thrown tests more specific

making error thrown tests more specific

--- test/parsers/chooseParser.test.coffee
+++ test/parsers/chooseParser.test.coffee
@@ -3,10 +3,10 @@
 describe 'chooseParser', ->

   it 'throws if input does not start with [', ->
-    expect(()->chooseParser.parse('hello')).to.throw Error
+    expect(()->chooseParser.parse('hello')).to.throw 'unexpected input'

   it 'throws if [ and ] are unmatched', ->
-    expect(()->chooseParser.parse('[hello')).to.throw Error
+    expect(()->chooseParser.parse('[hello')).to.throw 'missing ] after ['

   it 'creates a choose fragment with a single option', ->
     {fragment: fragment, remainder: remainder} = chooseParser.parse '[hello]'


Handle frameDeletion events

Handle frameDeletion events

--- elements/livesnooker-home.coffee
+++ elements/livesnooker-home.coffee
@@ -39,10 +39,11 @@
       frame = @league.get('Frames').get(event.detail.frame.id)
       frame.set(event.detail.frame)
       frame.populateAssociations()
-      this.querySelector("#unfinished").render()
-      this.querySelector("#finished").render()
     else if event.detail.event == 'frameDelete'
-      1
+      frame = @league.get('Frames').get(event.detail.frame.id)
+      frame.set(event.detail.frame)
+      frame.trigger('destroy', frame);
+      frame = null
     else if event.detail.event == 'newPlayer'
       1


Updated ping function

Updated ping function

--- modules/ping.coffee
+++ modules/ping.coffee
@@ -1,8 +1,8 @@
 class PingModule extends BotModule
   init: =>
     @registerCommand 'ping', (msg, args)->
-      sd = Date.now()
+      sd = new Date(msg.timestamp).getTime()
       msg.channel.sendMessage "Pong!"
-      .then (m)=> m.edit "Pong! `#{Date.now() - sd}ms`"
+      .then (m)=> m.edit "Pong! `#{new Date(m.timestamp).getTime() - sd}ms`"

 module.exports = PingModule


Guard against loading two Anchor Tabs on one page.

--- src/main/webapp/coffeescripts-hidden/load.coffee
+++ src/main/webapp/coffeescripts-hidden/load.coffee
@@ -235,6 +235,11 @@
   # safely without things getting hairy. If we find out in the future that things are wonky about that
   # then we'll have to refactor some of this code, I suppose.
   $ = jQuery # In case we're in compability mode.
+
+  # If the page we're on has done something incredibly stupid and runs this script twice, we need to
+  # bail so we don't load two Anchor Tabs on one page.
+  return if $("#anchor-tab").length > 0
+
   tabId = $("#anchortab-loader").data('tab-id')
   tabJson = "//" + apiDomain + "/api/v1/embed/" + tabId


Fixes #4721

Fixes #4721

--- packages/rocketchat-ui/views/app/room.coffee
+++ packages/rocketchat-ui/views/app/room.coffee
@@ -398,7 +398,7 @@

 	'dragenter .dropzone': (e) ->
 		types = e.originalEvent?.dataTransfer?.types
-		if types?.length > 0 and _.every(types, (type) => type.indexOf('text/') is -1) and userCanDrop this._id
+		if types?.length > 0 and _.every(types, (type) => type.indexOf('text/') is -1 or type.indexOf('text/uri-list') isnt -1) and userCanDrop this._id
 			e.currentTarget.classList.add 'over'

 	'dragleave .dropzone-overlay': (e) ->


fix for DnD files in Firefox

fix for DnD files in Firefox

--- packages/rocketchat-ui/views/app/room.coffee
+++ packages/rocketchat-ui/views/app/room.coffee
@@ -397,9 +397,15 @@
 			ChatMessage.update {_id: id}, {$set: {"urls.#{index}.collapsed": !collapsed}}

 	'dragenter .dropzone': (e) ->
-		items = e.originalEvent?.dataTransfer?.items
-		if items?.length > 0 and items?[0]?.kind isnt 'string' and userCanDrop this._id
-			e.currentTarget.classList.add 'over'
+		isChrome = navigator.userAgent.toLocaleLowerCase().indexOf('chrome') > -1
+
+		if isChrome
+			items = e.originalEvent?.dataTransfer?.items
+			if items?.length > 0 and items?[0]?.kind isnt 'string' and userCanDrop this._id
+				e.currentTarget.classList.add 'over'
+		else
+			if userCanDrop this._id
+				e.currentTarget.classList.add 'over'

 	'dragleave .dropzone-overlay': (e) ->
 		e.currentTarget.parentNode.classList.remove 'over'
@@ -415,9 +421,7 @@
 		event.currentTarget.parentNode.classList.remove 'over'

 		e = event.originalEvent or event
-		files = e.target.files
-		if not files or files.length is 0
-			files = e.dataTransfer?.files or []
+		files = e.dataTransfer?.files or []

 		filesToUpload = []
 		for file in files


export AIPO ACTIVITY, TIMELINE URL

export AIPO ACTIVITY, TIMELINE URL

--- scripts/aipo.coffee
+++ scripts/aipo.coffee
@@ -15,8 +15,8 @@
 query = require('querystring')
 cronJob = require('cron').CronJob

-PATH_ACTIVITY = '/portal/media-type/html/user/h_sakaguchi_admin/page/default.psml/js_peid/P-143fe3e7fc1-10a40?action=controls.Maximize'
-PATH_TIMELINE = '/portal/media-type/html/user/h_sakaguchi_admin/page/default.psml/js_peid/P-143fe3e7fc1-10a3f?action=controls.Maximize'
+PATH_ACTIVITY = process.env.HUBOT_AIPO_ACTIVITY_URL
+PATH_TIMELINE = process.env.HUBOT_AIPO_TIMELINE_URL

 # {{{ SenpaiStorage
 setSenpaiStorage = (robot, key, val) ->


if/THEN/else

if/THEN/else

--- src/colour-extractor.coffee
+++ src/colour-extractor.coffee
@@ -57,7 +57,7 @@

 exports.rgb2hex = (r, g, b) ->
   rgb = if arguments.length is 1 then r else [r, g, b]
-  '#' + rgb.map((x) -> (if x < 16 '0' else '') + x.toString(16)).join('')
+  '#' + rgb.map((x) -> (if x < 16 then '0' else '') + x.toString(16)).join('')

 exports.hex2rgb = (xs) ->
   xs = xs.slice(1) if xs[0] is '#'


Added ability to return Jump To links

Added ability to return Jump To links

Adjusted search to Google's regular search [removed API search]. Added User-Agent header to request to get correct page structure back.

--- scripts/docs.coffee
+++ scripts/docs.coffee
@@ -11,8 +11,11 @@
 #   None

 cheerio   = require 'cheerio'
-htmlStrip = require('htmlstrip-native');
-API_URL   = 'http://ajax.googleapis.com/ajax/services/search/web'
+htmlStrip = require 'htmlstrip-native'
+
+SEARCH_URL = 'https://www.google.com/search'
+# this gets the correct page structure, needs to be slimmed down
+USER_AGENT = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/34.0.1847.116 Chrome/34.0.1847.116 Safari/537.36'

 module.exports = (robot) ->
   getQueryUrl = (version, query) ->
@@ -21,18 +24,27 @@
     else if version == 'api'
       "site:laravel.com/api/4.1 #{query}"
     else if version == 'php'
-        "site:www.php.net/manual/en #{query}"
+      "site:www.php.net/manual/en #{query}"
     else if version?
       "site:laravel.com/docs/#{version} #{query}"
     else
       "site:laravel.com/docs #{query}"

   fetchResult = (query, callback) ->
-    robot.http(API_URL).query(v: '1.0', q: query).get() (err, res, body) ->
-      results = JSON.parse(body).responseData.results
-      url = results[0] and results[0].url
-      callback(url) if callback
+    robot.http(SEARCH_URL)
+      .header('User-Agent', USER_AGENT)
+      .query(q: query)
+      .get() (err, res, body) ->
+        $ = cheerio.load body
+        firstresult = $('li.g').first()
+        url = firstresult.find('h3.r a').attr('href')
+        jumpto = firstresult.find('.st .f a').attr('href')

+        if jumpto?
+          url = jumpto
+        callback url if callback
+
+  # this regex works fine, would like to fix the space after version though
   robot.hear /(([^:,\s!]+)[:,\s]+)?!docs\s([0-9.]+|api|dev|php)?\s?(.*)/i, (msg) ->
     user = msg.match[2]
     version = msg.match[3]
@@ -55,10 +67,10 @@
         response = user + ": " + response

       if version == 'php' and /function/.test url
-          robot.http(url).get() (err, res, body) ->
-            $ = cheerio.load body
-            methodSigContent = htmlStrip.html_strip $('.methodsynopsis').html(), compact_whitespace : true
-            msg.send response + " | #{methodSigContent}"
+        robot.http(url).get() (err, res, body) ->
+          $ = cheerio.load body
+          methodSigContent = htmlStrip.html_strip $('.methodsynopsis').html(), compact_whitespace : true
+          msg.send response + " | #{methodSigContent}"
       else
         if !url
           msg.send "No results for \"#{query}\""


Something was missing

Something was missing

--- strategies/BeggarGardens.coffee
+++ strategies/BeggarGardens.coffee
@@ -1,11 +1,14 @@
 {
   name: 'BeggarGardens'
-  author: 'ragingduckd'
+  author: 'ragingduckd', 'DStu'
   requires: ['Beggar', 'Gardens']

   gainPriority: (state, my) ->
     if state.supply["Rebuild"]?
-      return this.gainPriorityRebuild(state, my)
+      if my.turnsTaken < 3 or state.countInSupply("Rebuild") < 10
+        return this.gainPriorityRebuild(state, my)
+      else
+        return this.gainPriorityDefault(state, my)
     else
       return this.gainPriorityDefault(state, my)


FIXED LOUDS TO SUPPORT MORE :symbols:

FIXED LOUDS TO SUPPORT MORE :symbols:

https://regex101.com/r/fE2rE2/1

Use cases above.

--- scripts/loud.coffee
+++ scripts/loud.coffee
@@ -18,7 +18,7 @@
 #   annabunches

 module.exports = (robot) ->
-  robot.hear /^([A-Z][A-Z0-9 .,'"()\?\!-]+)$/, (res) ->
+  robot.hear /^([A-Z"][A-Z0-9 .,'"()\?!&%$#@+-]+)$/, (res) ->
     # Pick a loud from the stored list and say it. Skip if there are no louds.
     old_loud = res.random(robot.brain.get('louds'))
     if old_loud?


Minor: remove unnecessary function

Minor: remove unnecessary function

--- src/resources-packaged/xbl/orbeon/wpaint/wpaint.coffee
+++ src/resources-packaged/xbl/orbeon/wpaint/wpaint.coffee
@@ -41,9 +41,9 @@

     backgroundImageChanged: ->
         if @canvasSupported
-            imageSrc = @imageEl.attr('src')
+            imageSrc     = @imageEl.attr('src')
             imageIsEmpty = ! _.isNull(imageSrc.match(/spacer.gif$/))
-            if (@_imgIsEmpty(@imageEl))
+            if (imageIsEmpty)
                 @wpaintElA.addClass('xforms-hidden')
                 if not _.isNull(@wpaintElC)
                     @wpaintElC.detach()
@@ -56,8 +56,8 @@
                 @wpaintElC = $('<div class="fr-wpaint-container-c" tabindex="-1"/>')
                 @wpaintElB.append(@wpaintElC)
                 @wpaintElC.on('focusout', => @blur())
-                @wpaintElC.css('width',  @imageEl.width()  + 'px')
-                @wpaintElC.css('height', @imageEl.height() + 'px')
+                @wpaintElC.css('width',  @imageEl.width() )
+                @wpaintElC.css('height', @imageEl.height())
                 annotation = @annotationEl.attr('src')
                 imgData = @wpaintElC.wPaint
                     drawDown : => @drawDown()
@@ -74,10 +74,6 @@
     blur: ->
         @_sendAnnotationToServer()

-    _imgIsEmpty: (imgEl) ->
-        imgSrc = imgEl.attr('src')
-        ! _.isNull(imgSrc.match(/spacer.gif$/))
-
     # Send the image data from wPaint to the server, which will put it in <annotation>
     _sendAnnotationToServer: ->
         annotationImgData = @wpaintElC.wPaint('image')


TOOM-2130 add error logging to beforeEach block

--- src/spec/integration/product-type-importer.spec.coffee
+++ src/spec/integration/product-type-importer.spec.coffee
@@ -77,6 +77,10 @@
         sphereClient.productTypes.byId(productType.id)
         .delete(productType.version)
       , concurrency: 5
+      .then ->
+        console.log "Product types were deleted"
+      .catch (err) ->
+        console.error "There was an error while deleting product types", err

   it 'should import product type', ->
     sphereClient.productTypes.fetch()


better metrics for axis element extents

better metrics for axis element extents

--- static/coffee/renderers/guide/linear_axis.coffee
+++ static/coffee/renderers/guide/linear_axis.coffee
@@ -188,6 +188,7 @@

     angle = _angle_lookup[side][orient]
     standoff = @_tick_extent() + @_tick_label_extent() + @mget('axis_label_standoff')
+
     sx = (sx[0] + sx[sx.length-1])/2
     sy = (sy[0] + sy[sy.length-1])/2

@@ -268,8 +269,8 @@
       for i in [0..labels.length-1]
         if not labels[i]?
           continue
-        w = @plot_view.ctx.measureText(labels[i]).width * 1.3 # height overestimates, compensate
-        h = @plot_view.ctx.measureText(labels[i]).ascent
+        w = @plot_view.ctx.measureText(labels[i]).width * 1.1
+        h = @plot_view.ctx.measureText(labels[i]).ascent * 0.9
         val = w*s + (h/factor)*c
         if val > extent
           extent = val
@@ -277,8 +278,8 @@
       for i in [0..labels.length-1]
         if not labels[i]?
           continue
-        w = @plot_view.ctx.measureText(labels[i]).width * 1.3 # height overestimates, compensate
-        h = @plot_view.ctx.measureText(labels[i]).ascent
+        w = @plot_view.ctx.measureText(labels[i]).width * 1.1
+        h = @plot_view.ctx.measureText(labels[i]).ascent * 0.9
         val = w*c + (h/factor)*s
         if val > extent
           extent = val
@@ -286,9 +287,7 @@
     if extent > 0
       extent += @mget('major_label_standoff')

-    rounding = @mget('rounding_value')
-
-    return (Math.floor(extent/rounding) + 1) * rounding
+    return extent

   _axis_label_extent: () ->
     extent = 0
@@ -305,18 +304,14 @@
     if @mget('axis_label')
       extent += @mget('axis_label_standoff')
       @axis_label_props.set(@plot_view.ctx, @)
-      w = @plot_view.ctx.measureText(@mget('axis_label')).width
-      h = @plot_view.ctx.measureText(@mget('axis_label')).ascent
+      w = @plot_view.ctx.measureText(@mget('axis_label')).width * 1.1
+      h = @plot_view.ctx.measureText(@mget('axis_label')).ascent * 0.9
       if side == "top" or side == "bottom"
         extent += w*s + h*c
       else
         extent += w*c + h*s

     return extent
-
-    # rounding = @mget('rounding_value')
-
-    # return (Math.floor(extent/rounding) + 1) * rounding

   _padding_request: () ->
     req = {}


pagination complete

pagination complete

--- client/landing/app/Applications/Activity.kdapplication/activitylistcontroller.coffee
+++ client/landing/app/Applications/Activity.kdapplication/activitylistcontroller.coffee
@@ -107,11 +107,24 @@

     @checkIfLikedBefore activityIds

-    modifiedAt = activities.first.meta.createdAt
-    unless @lastItemTimeStamp > modifiedAt
-      # HACK: activity timestamp is off -200 ms on first get
-      time = new Date(modifiedAt).getTime()+1000
-      @lastItemTimeStamp = new Date(time).toISOString()
+    console.log("trying to set lastitemtimestamp - ", @lastItemTimeStamp)
+    pivot = @lastItemTimeStamp || (new Date()).getTime()
+    console.log("my pivot - " + pivot)
+    for obj in activities
+      obj_ts = (new Date(obj.meta.createdAt)).getTime()
+      console.log(">>>>", obj_ts, pivot, obj_ts < pivot, (new Date(obj.meta.createdAt)))
+      if obj_ts < pivot
+        console.log("changing pivot")
+        pivot = obj_ts
+    @lastItemTimeStamp = pivot
+    console.log("lastitemtimestamp is now - ", @lastItemTimeStamp)
+
+    #modifiedAt = activities.first.meta.createdAt
+    #
+    #unless @lastItemTimeStamp > modifiedAt
+    #  # HACK: activity timestamp is off -200 ms on first get
+    #  time = new Date(modifiedAt).getTime()+1000
+    #  @lastItemTimeStamp = new Date(time).toISOString()

     KD.logToMixpanel "populateActivity.success", 5

@@ -154,10 +167,12 @@
           likeView._currentState = yes

   getLastItemTimeStamp: ->
-
-    if item = hiddenItems.first
+    item = hiddenItems.first
+    if item
+      console.log("hidden items first ")
       item.getData().createdAt or item.getData().createdAtTimestamps.last
     else
+      console.log("last time stamp - " + @lastItemTimeStamp)
       @lastItemTimeStamp

   followedActivityArrived: (activity) ->


real time update of new member bucket threw an exception

--- client/landing/app/Applications/Activity.kdapplication/activitylistcontroller.coffee
+++ client/landing/app/Applications/Activity.kdapplication/activitylistcontroller.coffee
@@ -118,7 +118,8 @@
       for item in @itemsOrdered
         if item.getData() instanceof NewMemberBucketData
           data = item.getData()
-          data.anchors.pop()
+          if data.count > 3
+            data.anchors.pop()
           data.anchors.unshift bucket.anchor
           data.count++
           item.slideOut =>


adds better error message for snapshotter

--- source/infrastructure/snapshotter.coffee
+++ source/infrastructure/snapshotter.coffee
@@ -1,4 +1,7 @@
 class Space.eventSourcing.Snapshotter extends Space.Object
+
+  ERRORS:
+    noSnapshotFound: (id) -> new Error "No snapshot was found for aggregate <#{id}>"

   _collection: null
   _versionFrequency: 0
@@ -8,7 +11,7 @@
     @_versionFrequency = config.versionFrequency

   makeSnapshotOf: (aggregate) ->
-    id = aggregate.getId()
+    id = aggregate.getId().toString()
     currentVersion = aggregate.getVersion()
     data = @_collection.findOne _id: id

@@ -20,4 +23,9 @@
       @_collection.insert _id: id, snapshot: aggregate.getSnapshot()

   getSnapshotOf: (Type, id) ->
-    return Type.createFromSnapshot @_collection.findOne(_id: id).snapshot
+    id = id.toString()
+    record = @_collection.findOne(_id: id)
+    if record?
+      return Type.createFromSnapshot record.snapshot
+    else
+      throw @ERRORS.noSnapshotFound(id)


added support for spec-level processors

added support for spec-level processors

--- src/crystal/generate.coffee
+++ src/crystal/generate.coffee
@@ -537,15 +537,23 @@
 parse = (spec, project, processors) ->
 	for i of spec
 		s = spec[i]
-		if typeof(s) == 'object'
+
+		if typeof(s) == 'object' and !s['$processor']
 			spec[i] = parse(spec[i], project, processors)
-		else
-			if typeof(s) == 'string'
-				if s.substr(0, 1) == '$' && project[s.substr(1)]
-					spec[i] = project[s.substr(1)]
-		if processors and processors.length
-			for processor in processors
-				spec[i] = processor.callback spec[i]
+		else if typeof(s) == 'string' && s.substr(0, 1) == '$' && project[s.substr(1)]
+			spec[i] = project[s.substr(1)]
+
+		if spec[i]['$processor']
+			if typeof(spec[i]['$processor']) == 'string'
+				spec[i]['$processor'] = [spec[i]['$processor']]
+
+			for proc in spec[i]['$processor']
+				for processor in processors
+					if processor.alias == proc
+						spec[i]['$value'] = processor.callback spec[i]['$value']
+
+			spec[i] = spec[i]['$value']
+
 	spec

 sortObject = (object) ->


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -23,8 +23,8 @@
         radiuso = 130                           # Outer radius of the pie
         radiusi = 65                            # Inner radius of the pie (zero = pie, non-zero = donut)
         color = d3.scale.category20()           # Color scale for pie slices
-        dropshadowx = 4                         # X-offset for drop shadow filter
-        dropshadowy = 4                         # Y-offset for drop shadow filter
+        dropshadowx = 14                         # X-offset for drop shadow filter
+        dropshadowy = 14                         # Y-offset for drop shadow filter
         dropshadowblur = "1.1"                  # [STRING] Blur value for drop shadow filter

         pie = d3.layout.pie().value((d) -> d.value)


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -26,11 +26,11 @@
         $(@node).children("svg").remove();

         width = @width
-        height = @height
-        radius = @radius
-        radiuso = @radiuso
-        radiusi = @radiusi
-        color = @color
+        height = 400                           # Height of the SVG area
+        radius = Math.min(width, height) / 2   # Calculated min dimension of the SVG area
+        radiuso = 130                          # Outer radius of the pie
+        radiusi = 65                           # Inner radius of the pie (zero = pie, non-zero = donut)
+        color = d3.scale.category20()          # Color scale for pie slices
         pie = d3.layout.pie().value((d) -> d.value)
         arc = d3.svg.arc().innerRadius(radiusi).outerRadius(radiuso)
         svg = d3.select(@node).append('svg').attr('width', width).attr('height', height).append('g').attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')')


trim edit

trim edit

--- games/edit.coffee
+++ games/edit.coffee
@@ -5,31 +5,9 @@
 #
 # Role: explain how to create and share your own levels
 #
-# Key Interface Elements:
-# * edit / save
-# * action bricks (forward, right, left)
-# * run program (run loop)
-# * bump program (interrupt handler)
-
-# TODO:
-# Gate by default should send _victory action when interrupted
-# Turtle defaults to reverse on interrupt, to avoid invisible loop
-# Implicitly add: assume, per-level turtle and gate (if absent)
-# Check and send 'done -1' if infinite loop (e.g., no interrupt)
-
-# COMMENT
-# Scores for each level are based on analogs of real-world metrics
-# - Clicks (programmer hours)
-# - Bricks (lines of code)
-# - Ticks (execution time)
-# [with apologies to Dr. Seuss and "Fox in Socks"]
-#
-# Advanced players seeking to optimize their score will need to make
-# similar tradeoffs to what we faced in writing this app!
-#

 exports.game = {
-  name: 'Use "Edit" to Modify Level'
+  name: 'Use "Edit" to Modify Levels'
   assume: 'baseline'
   comment: "The followup Hour of NODE tutorial"
   author: {name: 'Ernest Prabhakar', url: 'https://github.com/drernie'}
@@ -40,23 +18,7 @@

   levels: [
     {
-      name: 'Click "Play" to Move Turtle to Exit Pad'
-      comment: '
-      Teach kids to use a run loop rather than direct manipulation.
-      They can also experiment with the other control buttons.
-      Everything else is disabled to avoid confusion.
-      '
-      message: 'You did it!'
-      bricks: 1
-      action_limit: 1
-      goal: { clicks: 0, bricks: 1, ticks: 4 }
-      sprites: [
-        { kind: 'gate', actions: {interrupt: ['_victory']}, position: [1,1] }
-        { kind: 'turtle', actions: {run: ['forward', 'right']}, editable: false }
-      ]
-    }
-    {
-      name: 'Click "Edit" to Modify. Click Sprite to Modify.'
+      name: 'Click "Edit" to Modify and "Save" when Done.'
       comment: '
       Now students know enough to start designing their own levels.
       - Click "Edit" to enable design mode.


prefer string manipulation and arrays to regexp

--- server/controllers/deploy.coffee
+++ server/controllers/deploy.coffee
@@ -1,15 +1,12 @@
 AppConfig = require('../initializers/config')
 Deploy = require('../lib/deploy')
 range_check = require('range_check')
+_ = require('underscore')

 tagRefersToServer = (tag, serverTags) ->
-  regexp =  "^"                         # at beginning of line
-  regexp += "[^-]*"                     # accept everything until first hyphen
-  regexp += "(#{serverTags.join('|')})" # meanwhile, look for one of the tags
-  regexp += "[,-]"                      # if found, be sure it is either followed by another one or is the last
-
-  regexpMatches = new RegExp(regexp).exec(tag)
-  return regexpMatches?[1]?
+  deploymentTags = tag.split('-')[0]
+  deploymentTags = deploymentTags.split(',')
+  return _.intersection(deploymentTags, serverTags).length != 0

 getIpFromRequest = (req) ->
   req.headers['x-real-ip'] or req.connection.remoteAddress


fix grammar

fix grammar

--- grammars/nlf.cson
+++ grammars/nlf.cson
@@ -9,31 +9,31 @@
   }
   {
     match: "\\b((0(x|X)[0-9a-fA-F]+)|([0-9]+(\\.[0-9]+)?))\\b"
-    name: "constant.numeric.nsis"
+    name: "constant.numeric.nlf"
   }
   {
-    undefined: "match"
-    "\\$_[\\w]+": "name"
+    match: "\\$_[\\w]+"
+    name: "entity.name.function.nlf"
   }
   {
-    undefined: "match"
-    "\\$[\\w]+": "name"
+    match: "\\$[\\w]+"
+    name: "storage.type.function.nlf"
   }
   {
-    undefined: "match"
-    "\\$\\(\\^[\\w]+\\)": "name"
+    match: "\\$\\(\\^[\\w]+\\)"
+    name: "string.value.nlf"
   }
   {
-    undefined: "match"
-    "\\%[\\w]+": "name"
+    match: "\\%[\\w]+"
+    name: "string.replacement.nlf"
   }
   {
-    undefined: "match"
-    "([\\w]?&[\\w]+)": "name"
+    match: "([\\w]?&[\\w]+)"
+    name: "constant.shortcut.nlf"
   }
   {
-    undefined: "match"
-    "\\\\.": "name"
+    match: "\\\\."
+    name: "constant.character.escape.nlf"
   }
 ]
 scopeName: "source.nlf"


Fixed a bug in IDify.

Fixed a bug in IDify.

--- server/users/user_handler.coffee
+++ server/users/user_handler.coffee
@@ -232,7 +232,7 @@

   IDify: (idOrSlug, done) ->
     return done null, idOrSlug if Handler.isID idOrSlug
-    User.getBySlug idOrSlug, (err, user) -> done err, user?.get '_id'
+    User.findBySlug idOrSlug, (err, user) -> done err, user?.get '_id'

   getLevelSessions: (req, res, userIDOrSlug) ->
     @IDify userIDOrSlug, (err, userID) =>


:bug:Add user getClans undefined check

:bug:Add user getClans undefined check

--- server/users/user_handler.coffee
+++ server/users/user_handler.coffee
@@ -541,10 +541,10 @@

   getClans: (req, res, userIDOrSlug) ->
     @getDocumentForIdOrSlug userIDOrSlug, (err, user) =>
-      return @sendNotFoundError(res) if not user
+      return @sendNotFoundError(res) unless user
       clanIDs = user.get('clans') ? []
       query = {$and: [{_id: {$in: clanIDs}}]}
-      query['$and'].push {type: 'public'} unless req.user.id is user.id
+      query['$and'].push {type: 'public'} unless req.user?.id is user.id
       Clan.find query, (err, documents) =>
         return @sendDatabaseError(res, err) if err
         @sendSuccess(res, documents)


Mispelt debug message..

Mispelt debug message..

--- server/controllers/deploy.coffee
+++ server/controllers/deploy.coffee
@@ -7,7 +7,7 @@
 exports.index = (req, res) ->
   parsedPayload = JSON.parse(req.body.payload)

-  console.log "Got deploy message from #{parsePayload.ref}"
+  console.log "Got deploy message from #{parsedPayload.ref}"
   unless getBranchFromRef(parsedPayload.ref) is "deploy"
     console.log "Ignoring, only deploys on pushes from deploy"
     return res.send 500, "Only commits from deploy branch are accepted"


Use req.path instead of req.url in server side route matching

--- server/ssr/renderOnServer.coffee
+++ server/ssr/renderOnServer.coffee
@@ -89,7 +89,7 @@

     try
         Routes.some((route) ->
-            match = matchPath(req.url, route)
+            match = matchPath(req.path, route)
             if (match)
                 foundMatch = match
                 component = route.component


Move cannon upward

Move cannon upward

--- src/components/entities/DroneShip.coffee
+++ src/components/entities/DroneShip.coffee
@@ -68,7 +68,7 @@

     @attach Crafty.e('2D, TurretPlace').attr(
       x: @x + 370
-      y: @y + 84
+      y: @y + 74
       z: 15
       w: 15
       h: 2


silly print removal

silly print removal

--- src/controllers/thread_controller.coffee
+++ src/controllers/thread_controller.coffee
@@ -52,11 +52,9 @@
   req.user.getThreads({where: {id: req.param('threadId')}}).then (thread) ->
     if not thread?
       throw "User not allowed to retrieve messages for this thread."
-    # Limit the number of messages returned, default to 50 latest
-    console.log thread
-

     if req.param('limit')
+      # Limit the number of messages returned, default to 50 latest
       limit = Math.min parseInt(req.param('limit'), 10), 50
     else
       limit = 50


Add an example `redis.ratelimiter` config.

--- services/web/config/settings.defaults.coffee
+++ services/web/config/settings.defaults.coffee
@@ -39,6 +39,16 @@
 			password: ""

 		# websessions:
+		# 	cluster: [
+		# 		{host: 'localhost', port: 7000}
+		# 		{host: 'localhost', port: 7001}
+		# 		{host: 'localhost', port: 7002}
+		# 		{host: 'localhost', port: 7003}
+		# 		{host: 'localhost', port: 7004}
+		# 		{host: 'localhost', port: 7005}
+		# 	]
+
+		# ratelimiter:
 		# 	cluster: [
 		# 		{host: 'localhost', port: 7000}
 		# 		{host: 'localhost', port: 7001}


fix \\n escape

fix \\n escape

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

 argv = require('minimist')(process.argv.slice(2))
-if !argv._[0]
+input = argv._[0]
+if !input
   console.error """
     -i    print input
     -t    print tokens
@@ -20,9 +21,12 @@
     """
   process.exit 1

+# some escape remove
+input = input.replace /\\n/g, "\n"
+
 if argv.i
-  p magenta "Input:", cyan.bold argv._[0]
-await tokenize argv._[0], {}, defer err, tok_res
+  p magenta "Input:", cyan.bold input
+await tokenize input, {}, defer err, tok_res
 throw err if err
 if argv.t
   p "Token list:"


minor: adding link to github repo

minor: adding link to github repo

--- canvg.coffee
+++ canvg.coffee
@@ -5,7 +5,9 @@

 # original Javascript code by:
 # Gabe Lerner (gabelerner@gmail.com)
-# http://code.google.com/p/canvg/
+#   http://code.google.com/p/canvg/
+# and subsequently:
+#   https://github.com/gabelerner/canvg

 # canvg(target, s)
 # empty parameters: replace all 'svg' elements on page with 'canvas' elements


Changed Hinterlands treasures to derive from `treasure` rather than `c.Silver` or `c.Gold` so that their amount in the starting supply is 10, rather than 40 or 30.

Fixed IGG to gain the Copper to hand.

--- cards.coffee
+++ cards.coffee
@@ -446,15 +446,16 @@
     state.log("...which is worth #{this.getCoins(state)}.")
 }

-makeCard 'Cache', c.Gold, {
-  cost: 5
+makeCard 'Cache', treasure, {
+  cost: 5
+  coins: 3

   gainEffect: (state) ->
     state.gainCard(state.current, c.Copper)
     state.gainCard(state.current, c.Copper)
 }

-makeCard "Fool's Gold", c.Silver, {
+makeCard "Fool's Gold", treasure, {
   isReaction: true
   cost: 2
   coins: 1
@@ -498,12 +499,12 @@
       state.log("...#{state.current.ai} trashes the Horn of Plenty.")
 }

-makeCard 'Ill-Gotten Gains', c.Silver, {
+makeCard 'Ill-Gotten Gains', treasure, {
   cost: 5
   coins: 1
   playEffect: (state) ->
     if state.current.ai.choose('gainCopper', state, [yes, no])
-      state.current.gainCard(c.Copper)
+      state.gainCard(state.current, c.Copper, 'hand')

   gainEffect: (state) ->
     # For each player but the current: gain a curse.


small code simplification

small code simplification

--- src/lib/notification.coffee
+++ src/lib/notification.coffee
@@ -26,7 +26,7 @@
     # Style the tip and insert a relevant internationalized string depending on
     # the outcome of the copy request or the existence of an override message.
     div.className = result;
-    div.innerHTML = if ext.message then ext.message else utils.i18n result
+    div.innerHTML = ext.message or utils.i18n result
     # Reset `ext` to avoid affecting copy other copy requests. If the user has
     # disabled the notifications option this should still be called for safety.
     ext.reset()


simplify code for testing

simplify code for testing

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -106,7 +106,8 @@
     if tutorialDone
       @tutorial.end()
       Subject.next() if doingTutorial or not Subject.current
-      AnalyticsLogger.logEvent('test','test2','test','test')
+      alert "changed code"
+#AnalyticsLogger.logEvent('test','test2','test','test')
     else
       getTutorialSubject().select()


Fix "blue" step rendering

Fix "blue" step rendering

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -320,7 +320,8 @@
   renderBlue: (blue) =>
     @blueButtons.removeClass 'selected'
     if blue?
-      @blueButtons.filter("[value='#{blue}']").addClass 'selected'
+      toSelect = @blueButtons.filter "[value='#{blue}']"
+      toSelect.prevAll().andSelf().addClass 'selected'

   setupCurve: =>
     @el.attr 'data-step': 'curve'


Added tests for 'boxRandom', 'boxRain', 'boxRainReverse', 'boxRainOutIn', 'boxRainInOut', 'boxRainGrow', 'boxRainGrowReverse', 'boxRainGrowOutIn', 'boxRainGrowInOut' (#6).

--- spec/rambling.slider.transitions.spec.coffee
+++ spec/rambling.slider.transitions.spec.coffee
@@ -414,10 +414,14 @@
       sort_callback_setter = (callback) -> sort_callback = callback
       animation_helpers =
         animateFullImage: ->
+        fadeBoxes: ->
+        rainBoxes: ->

       spyOn($.fn, 'css').andCallFake -> @
       spyOn($.fn, 'animate').andCallFake -> @
       spyOn(animation_helpers, 'animateFullImage').andCallFake (callback) -> animation_set_up_callback = callback
+      spyOn animation_helpers, 'fadeBoxes'
+      spyOn animation_helpers, 'rainBoxes'

       $.each all_around_transitions, (index, element) ->
         animation_helpers[element.helper] = ->
@@ -582,3 +586,65 @@

         it 'should unbind from the rambling:finished event', ->
           expect($.fn.unbind).toHaveBeenCalledWith 'rambling:finished', finished_callback
+
+    describe 'and executing box random showing', ->
+      beforeEach ->
+        image_transitions.boxRandom.apply animation_helpers
+
+      it 'should fade in the shuffled boxes', ->
+
+    describe 'and executing a box rain', ->
+      beforeEach ->
+        image_transitions.boxRain.apply animation_helpers
+
+      it 'should rain the boxes with default order', ->
+        expect(animation_helpers.rainBoxes).toHaveBeenCalledWith
+
+    describe 'and executing a reversed box rain', ->
+      beforeEach ->
+        image_transitions.boxRainReverse.apply animation_helpers
+
+      it 'should rain the boxes with reversed order', ->
+        expect(animation_helpers.rainBoxes).toHaveBeenCalledWith $.fn.reverse
+
+    describe 'and executing a outer to inner box rain', ->
+      beforeEach ->
+        image_transitions.boxRainOutIn.apply animation_helpers
+
+      it 'should rain the boxes from outer to inner', ->
+        expect(animation_helpers.rainBoxes).toHaveBeenCalledWith $.fn.sortOutIn
+
+    describe 'and executing a inner to outer box rain', ->
+      beforeEach ->
+        image_transitions.boxRainInOut.apply animation_helpers
+
+      it 'should rain the boxes from inner to outer', ->
+        expect(animation_helpers.rainBoxes).toHaveBeenCalledWith $.fn.sortInOut
+
+    describe 'and executing a box growing rain', ->
+      beforeEach ->
+        image_transitions.boxRainGrow.apply animation_helpers
+
+      it 'should rain the growing boxes with default order', ->
+        expect(animation_helpers.rainBoxes).toHaveBeenCalledWith undefined, true
+
+    describe 'and executing a reversed box growing rain', ->
+      beforeEach ->
+        image_transitions.boxRainGrowReverse.apply animation_helpers
+
+      it 'should rain the growing boxes with reversed order', ->
+        expect(animation_helpers.rainBoxes).toHaveBeenCalledWith $.fn.reverse, true
+
+    describe 'and executing a outer to inner box growing rain', ->
+      beforeEach ->
+        image_transitions.boxRainGrowOutIn.apply animation_helpers
+
+      it 'should rain the growing boxes from outer to inner', ->
+        expect(animation_helpers.rainBoxes).toHaveBeenCalledWith $.fn.sortOutIn, true
+
+    describe 'and executing a inner to outer box growing rain', ->
+      beforeEach ->
+        image_transitions.boxRainGrowInOut.apply animation_helpers
+
+      it 'should rain the growing boxes from inner to outer', ->
+        expect(animation_helpers.rainBoxes).toHaveBeenCalledWith $.fn.sortInOut, true


well, that doesn't make sense now

well, that doesn't make sense now

--- js/outer/about.coffee
+++ js/outer/about.coffee
@@ -57,7 +57,7 @@
       twitter: "DanielWoelfel"
       email: "daniel@circleci.com"
       bio: [
-        "Despite all our coercions, promises, bribes, and outright threats for nearly a year, it took us putting Daniel in a time-out and forcing him to type things about himself to get a bio out of him. He doesn't really have any hobbies: if he's doing something, it's usually either reading romantic fiction (not to be confused with romance novels!), coding, or binge-watching episodes of tv-show-he-just-discovered-that-is-so-awesome-he-can't-put-it-down. One of his first programming projects was an app written in Python that creates an exact replica of an image using only html tables. It's been almost 2 years since he last touched the thing, and it's still cranking out tables. He's only been rejected from YCombinator 3 times, which is empirically below the threshold of consolation prizes.",
+        "Despite all our coercions, promises, bribes, and outright threats for nearly a year, it took us putting Daniel in a time-out and forcing him to type things about himself to get a bio out of him. He doesn't really have any hobbies: if he's doing something, it's usually either reading romantic fiction (not to be confused with romance novels!), coding, or binge-watching episodes of tv-show-he-just-discovered-that-is-so-awesome-he-can't-put-it-down. One of his first programming projects was an app written in Python that creates an exact replica of an image using only html tables. It's been almost 2 years since he last touched the thing, and it's still cranking out tables.",
         "Daniel has a bachelors degree in Math from Texas A&M. He has written an app to graph Kickstarter projects, and was a customer (the 155th!) before he joined the Circle team. He looks forward to helping increase the 'wow, I can do something cool with this!' feeling that users get when they use Circle, and has been tinkering with a few things recently to make that possible. He currently helps feed his movie habit by helping organize weekly movie nights at the office. Next up: Cyrano de Bergerac."
       ]
     ,


Checkbox: Code styling.

Checkbox: Code styling.

--- src/lib/components/inputs/Checbox.coffee
+++ src/lib/components/inputs/Checbox.coffee
@@ -3,12 +3,11 @@
 goog.require 'spark.components.Field'

-###*
-  Checkbox component of Spark Framework.
-###
 class spark.components.Checkbox extends spark.components.Field

   ###*
+    Checkbox component of Spark Framework.
+
     @constructor
     @export
     @param   {Object=} options Class options.
@@ -28,6 +27,7 @@

   ###*
     Check the element.
+
     @export
   ###
   check: ->
@@ -37,6 +37,7 @@

   ###*
     Uncheck the element.
+
     @export
   ###
   uncheck: ->
@@ -46,6 +47,7 @@

   ###*
     Returns element's checked state.
+
     @export
     @return {boolean} Checked state.
   ###


Updates from downstream

Updates from downstream

--- vendor/assets/javascripts/fustrate/record.coffee
+++ vendor/assets/javascripts/fustrate/record.coffee
@@ -24,18 +24,21 @@
       @extractFromData(response)
       @_loaded = true

-  save: (params = {}) =>
-    url = if @id
-      @path(format: 'json')
+  update: (attributes = {}) =>
+    if @id
+      url = @path(format: 'json')
     else
-      Routes[@constructor.create_path](format: 'json')
+      @extractFromData(attributes)
+      url = Routes[@constructor.create_path](format: 'json')

-    data = @_toFormData()
-    data.append(key, value) for own key, value of params if params
-
+    if @community and attributes.community_id is undefined
+      attributes.community_id = @community.id
+
+    formData = @_toFormData(new FormData, attributes, @constructor.paramKey())
+
     $.ajax
       url: url
-      data: data
+      data: formData
       processData: false
       contentType: false
       method: if @id then 'PATCH' else 'POST'
@@ -48,33 +51,43 @@
         xhr
     .done @extractFromData

-  update: (data, params) =>
-    @extractFromData(data)
-    @save(params)
-
   delete: =>
     $.ajax @path(format: 'json'),
       method: 'DELETE'

-  toParams: -> {}
-
   _toFormData: (data, object, namespace) =>
-    data ?= new FormData
-    object ?= @toParams()
-
     for own field, value of object when typeof value isnt 'undefined'
       key = if namespace then "#{namespace}[#{field}]" else field

       if value and typeof value is 'object'
-        if value instanceof Array
-          data.append "#{key}[]", array_value for array_value in value
-        else if value instanceof File
-          data.append key, value
-        else
-          @_toFormData(data, value, key)
+        @_appendObjectToFormData(data, key, value)
       else if typeof value is 'boolean'
         data.append key, Number(value)
       else if value isnt null and value isnt undefined
         data.append key, value

     data
+
+  _appendObjectToFormData: (data, key, value) =>
+    if value instanceof Array
+      data.append "#{key}[]", array_value for array_value in value
+    else if value instanceof File
+      data.append key, value
+    else if moment.isMoment(value)
+      data.append key, value.format()
+    else if not (value instanceof Fustrate.Record)
+      @_toFormData(data, value, key)
+
+  @paramKey: ->
+    @class.underscore().replace('/', '_')
+
+  @buildList: (items, additional_attributes = {}) ->
+    for item in items
+      new @ $.extend(true, {}, item, additional_attributes)
+
+  @create: (attributes) ->
+    record = new @
+
+    $.Deferred (@deferred) =>
+      record.update(attributes).fail(@deferred.reject).done =>
+        @deferred.resolve record


Set default affiliation to 'none' for anon users

--- src/local/operations.coffee
+++ src/local/operations.coffee
@@ -156,7 +156,10 @@
         unless @req.node
             return cb()

-        if @req.actor.indexOf('@') >= 0
+        if @req.actorType is 'anonymous'
+            @actorAffiliation = 'none'
+
+        else if @req.actorType is 'user'
             t.getAffiliation @req.node, @req.actor, (err, affiliation) =>
                 if err
                     return cb err


Random nml ids changed to counter for module-cache caches

--- src/nml-parser/index.coffee
+++ src/nml-parser/index.coffee
@@ -16,6 +16,7 @@
 {BINDING_THIS_TO_TARGET_OPTS} = bindingParser

 ids = idsKeys = itemsKeys = extensions = queries = null
+fileUid = 0

 isAnchor = (obj) ->
     assert obj.type is ATTRIBUTE, "isAnchor: type must be an attribute"
@@ -164,7 +165,7 @@
             value

         unless elem.id
-            json.id = elem.id = "i#{utils.uid()}"
+            json.id = elem.id = "i#{fileUid++}"

         for body in elem.body
             switch body.type
@@ -287,6 +288,7 @@
     ids

 exports = module.exports = (file, filename, opts) ->
+    fileUid = 0
     elems = parser file, filename
     codes = {}
     autoInitCodes = []
@@ -333,7 +335,7 @@
             autoInitCodes.push code
         else
             code += 'return _c.createItem\n'
-            uid = 'n' + utils.uid()
+            uid = 'n' + fileUid++
             id ||= uid
             if codes[id]?
                 id = uid


Fix reference to RedmineLoader._errorFetchProject().

--- src/coffee/services/redmineLoader.coffee
+++ src/coffee/services/redmineLoader.coffee
@@ -230,7 +230,7 @@
     ###
     _successFetchProject: (data) =>
       if not data.project and (not data.projects or data.projects.length is 0)
-        _errorFetchProject data
+        @_errorFetchProject data
         return null
       data.projects = data.projects or [data.project]
       Message.toast Resource.string("msgLoadProjectSuccess", [data.account.name, data.projects.length]), 3000


Add coverage for javascript tests

Add coverage for javascript tests

--- karma.conf.coffee
+++ karma.conf.coffee
@@ -27,6 +27,7 @@
       'static/angular/sortable.js',
       'static/js/jasmine-jquery.js',
       'static/js/uuid.js',
+      'karma/flows/helpers.coffee',

       # the code we are testing
       'static/coffee/flows/*.coffee',
@@ -35,7 +36,6 @@
       { pattern: 'media/test_flows/*.json', watched: true, served: true, included: false },

       # our test files
-      'karma/flows/helpers.coffee',
       'karma/flows/test_services.coffee',
       'karma/flows/test_directives.coffee',

@@ -49,7 +49,8 @@
     # preprocess matching files before serving them to the browser
     # available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
     preprocessors: {
-      '**/*.coffee': ['coffee']
+      'karma/**/*.coffee': ['coffee']
+      'static/**/*.coffee': ['coverage']
     }

     # this makes sure that we get coffeescript line numbers instead
@@ -64,8 +65,11 @@
     # test results reporter to use
     # possible values: 'dots', 'progress'
     # available reporters: https://npmjs.org/browse/keyword/karma-reporter
-    reporters: ['progress']
+    reporters: ['progress', 'coverage']

+    coverageReporter:
+      type: 'html'
+      dir: 'js-coverage/'

     # web server port
     port: 9876
@@ -97,3 +101,4 @@
     # Continuous Integration mode
     # if true, Karma captures browsers, runs the tests and exits
     singleRun: true
+


Use @props.width vs hardcoded 100

Use @props.width vs hardcoded 100

--- src/components/task-plan/learning-guide.cjsx
+++ src/components/task-plan/learning-guide.cjsx
@@ -37,7 +37,7 @@

     fields = guide.fields

-    space_between = 100/fields.length+1
+    space_between = @props.width/fields.length+1
     points = _.map(fields, (f,i)=>
         {
           x: Math.max(space_between * i + (space_between/4), 5)


fix(composer): Check for empty as well as original body

--- src/flux/stores/draft-editing-session.coffee
+++ src/flux/stores/draft-editing-session.coffee
@@ -151,7 +151,7 @@
     warnings = []
     errors = []
     allRecipients = [].concat(@_draft.to, @_draft.cc, @_draft.bcc)
-    bodyIsEmpty = @_draft.body is @draftPristineBody()
+    bodyIsEmpty = @_draft.body is @draftPristineBody() or @_draft.body is "<br>"
     forwarded = DraftHelpers.isForwardedMessage(@_draft)
     hasAttachment = @_draft.files?.length > 0 or @_draft.uploads?.length > 0


fix(focus): Don't clear focus unless change removes acct

--- src/flux/stores/focused-content-store.coffee
+++ src/flux/stores/focused-content-store.coffee
@@ -61,7 +61,7 @@

   constructor: ->
     @_resetInstanceVars()
-    @listenTo AccountStore, @_onClear
+    @listenTo AccountStore, @_onAccountsChange
     @listenTo WorkspaceStore, @_onWorkspaceChange
     @listenTo DatabaseStore, @_onDataChange
     @listenTo Actions.setFocus, @_onFocus
@@ -79,10 +79,19 @@

   # Inbound Events

-  _onClear: =>
-    @_focused = {}
-    @_keyboardCursor = {}
-    @trigger({ impactsCollection: -> true })
+  _onAccountsChange: =>
+    # Ensure internal consistency by removing any focused items that belong
+    # to accounts which no longer exist.
+    changed = []
+
+    for dict in [@_focused, @_keyboardCursor]
+      for collection, item of dict
+        if item and item.accountId and !AccountStore.accountForId(item.accountId)
+          delete dict[collection]
+          changed.push(collection)
+
+    if changed.length > 0
+      @trigger({ impactsCollection: (c) -> changed.includes(c) })

   _onFocusKeyboard: ({collection, item}) =>
     throw new Error("focusKeyboard() requires a Model or null") if item and not (item instanceof Model)


Fix props defined on the wrong component Set some defaults for subjectCount and skipOwner

--- app/partials/collection-card.cjsx
+++ app/partials/collection-card.cjsx
@@ -8,9 +8,7 @@
   displayName: 'FlexibleLink'

   propTypes:
-    subjectCount: React.PropTypes.number
     to: React.PropTypes.string.isRequired
-    skipOwner: React.PropTypes.bool

   contextTypes:
     geordi: React.PropTypes.object
@@ -35,6 +33,12 @@
     imagePromise: React.PropTypes.object.isRequired
     linkTo: React.PropTypes.string.isRequired
     translationObjectName: React.PropTypes.string.isRequired
+    subjectCount: React.PropTypes.number
+    skipOwner: React.PropTypes.bool
+
+  getDefaultProps: ->
+    subjectCount: 0
+    skipOwner: false

   collectionOwner: ->
     apiClient.type(@props.collection.links.owner.type).get(@props.collection.links.owner.id)


fix crash on drag/resize

fix crash on drag/resize

--- app/scripts/control/widget.coffee
+++ app/scripts/control/widget.coffee
@@ -36,7 +36,7 @@
   # update widgets
   $interval(
     ->
-      widget.update() for widget in $scope.widgets
+      widget.update() for widget in $scope.widgets when widget.render
     , 300
     )
 ])
@@ -52,8 +52,13 @@
       $(jQelm).css('top', widget.position.top)
     $(jQelm).draggable({
       containment: 'parent'
+      start: (event, ui) ->
+        widget.render = false
+        for series in widget.data
+          series.values = series.values.slice()
       stop: (event, ui) ->
         widget.position = ui.position
+        widget.render = true
       })
 )

@@ -61,8 +66,16 @@
 ->
   restrict: 'A',
   link: (scope, elm, attr) ->
+    widget = scope.widgets[scope.$index]
     jQelm = $(elm[0])
-    $(jQelm).resizable()
+    $(jQelm).resizable({
+      start: (event, ui) ->
+        widget.render = false
+        for series in widget.data
+          series.values = series.values.slice()
+      stop: (event, ui) ->
+        widget.render = true
+    })
 )

 .factory('widgetFactory',
@@ -88,6 +101,7 @@
       update: -> _.each(_data, (element, index, list) ->
         element.values =  element.peripheral.historyPairs())
       url: defaultURL.replace('type', String(type))
+      render: true
       position: null
       options: {
         chart: {


Fix angle at end of animation

Fix angle at end of animation

--- app/scripts/lib/BezierPath.coffee
+++ app/scripts/lib/BezierPath.coffee
@@ -48,7 +48,7 @@
     p

   angleOnPath: (path, location) ->
-    p1 = @pointOnPath(path, location)
+    p1 = @pointOnPath(path, Math.min(location, 0.99))
     p2 = @pointOnPath(path, Math.min(location + 0.01, 1.0))
     angle = Math.atan2(p1.y - p2.y, p1.x - p2.x)
     angle *= (180 / Math.PI)


Fix a typo in usepackage snippet

Fix a typo in usepackage snippet

--- snippets/latex.cson
+++ snippets/latex.cson
@@ -22,7 +22,7 @@
     'body': '\\\\smallskip'
   'usepackage':
     'prefix': 'use'
-    'body': '\\\\usepagage'
+    'body': '\\\\usepackage'
   'unnumbered eqn':
     'prefix': 'beq'
     'body': '\\\\[\n$0\n\\\\]'


I'm dumb

I'm dumb

--- src/local/model_postgres.coffee
+++ src/local/model_postgres.coffee
@@ -350,7 +350,7 @@
         unless user
             return cb(new Error("No user"))

-        @db.query "SELECT node, listener, subscription FROM subscriptions WHERE \"user\"=$1 AND temporary=FALSE ORDER BY updated DESC", [ user ], (err, res) ->
+        @db.query "SELECT node, listener, subscription FROM subscriptions WHERE \"user\"=$1 AND temporary=TRUE ORDER BY updated DESC", [ user ], (err, res) ->
             cb err, res?.rows

     ##


Fix typo in fixtures.

Fix typo in fixtures.

--- spec/fixtures/predictions.coffee
+++ spec/fixtures/predictions.coffee
@@ -1,6 +1,6 @@
 module.exports =
   # Encodes realistic entropies.
-  'after: for i in': [
+  'after for i in': [
     [0.337218, ["in", "range" ] ]
     [0.345281, ["in", "range", "(" ] ]
     [1.85889,  ["in", "range", "(", "5" ] ]


Update scanner.coffee

Update scanner.coffee

--- port_scan/coffee/scanner.coffee
+++ port_scan/coffee/scanner.coffee
@@ -1,7 +1,6 @@
 ##scanner.coffee
 fs = require 'fs'
 exec = require('child_process').exec
-#MongoClient = require('mongodb').MongoClient
 mongojs = require 'mongojs'
 http = require 'http'
 qjobs = require 'qjobs'
@@ -14,9 +13,6 @@
 scanOne = (host,next)->
   command = 'sudo nmap -sS -sU ' + host + ' -P0 | awk \'$2=="open" {print $1}\' | sed \'s/\\/...//g\' | xargs echo -n'
   exec command, (error, stdout, stderr) ->
-    #MongoClient.connect 'mongodb://127.0.0.1:27017/test', (err, db) ->
-      #if err then throw err
-      #collection = db.collection 'ips'
     db.ips.update {ip: host}, {$set: {port: stdout.split ' '}}, {upsert:true}, (err, docs) ->
       if err then throw err
       console.log host
@@ -32,12 +28,14 @@
     for ipdic in json_data.ips
       iplst.push ipdic.ip

+    #remove unused ip
     db.ips.find({},{'_id': false, 'port': false}).forEach (err, docs) ->
       if err then throw err
       if docs != null
         if docs.ip not in iplst
           db.ips.remove {ip: docs.ip}

+    #add jobs to q
     for ip in iplst
       q.add scanOne, ip

@@ -48,12 +46,6 @@
       console.log '...All jobs done'
       db.close()

-#    q.on 'jobStart', (args) ->
-#      console.log 'jobStart', args._jobId
-
-#    q.on 'jobEnd', (args) ->
-#      console.log 'jobEnd', args._jobId
-
     q.run()


Respond to all fake server requests

Respond to all fake server requests

--- spec/storage-manager-spec.coffee
+++ spec/storage-manager-spec.coffee
@@ -151,15 +151,42 @@
       subTaskAssignee = buildUser(name: 'Slice')
       subTask.set('assignee_ids', [subTaskAssignee.id])

-      respondWith server, "/api/tasks/#{mainTask.id}?include=assignees%2Csub_tasks%2Cproject", resultsFrom: "tasks", data: { results: resultsArray("tasks", [mainTask]), tasks: resultsObject([mainTask, subTask]), projects: resultsObject([mainProject]), users: resultsObject([mainTaskAssignee]) }
-      respondWith server, "/api/tasks?include=assignees&only=#{subTask.id}&apply_default_filters=false", resultsFrom: "tasks", data: { results: resultsArray("tasks", [subTask]), tasks: resultsObject([subTask]), users: resultsObject([subTaskAssignee]) }
-      respondWith server, "/api/projects?include=time_entries&only=#{mainProject.id}&apply_default_filters=false", resultsFrom: "projects", data: { results: resultsArray("projects", [mainProject]), time_entries: resultsObject([timeEntry]), projects: resultsObject([mainProject]) }
-      respondWith server, "/api/time_entries?include=task&only=" + timeEntry.id + "&apply_default_filters=false", resultsFrom: "time_entries", data: { results: resultsArray("time_entries", [timeEntry]), time_entries: resultsObject([timeEntry]), tasks: resultsObject([timeTask]) }
-
-      loader = manager.loadModel "task", mainTask.id, include: ["assignees", {"sub_tasks": ["assignees"]}, { "project" : [{ "time_entries": ["task"] }] }]
+      respondWith server, "/api/tasks/#{mainTask.id}?include=assignees%2Csub_tasks%2Cproject",
+        resultsFrom: "tasks"
+        data:
+          results: resultsArray("tasks", [mainTask])
+          tasks: resultsObject([mainTask, subTask])
+          projects: resultsObject([mainProject])
+          users: resultsObject([mainTaskAssignee])
+      respondWith server, "/api/tasks?include=assignees&only=#{subTask.id}&apply_default_filters=false",
+        resultsFrom: "tasks"
+        data:
+          results: resultsArray("tasks", [subTask])
+          tasks: resultsObject([subTask])
+          users: resultsObject([subTaskAssignee])
+      respondWith server, "/api/projects?include=time_entries&only=#{mainProject.id}&apply_default_filters=false",
+        resultsFrom: "projects"
+        data:
+          results: resultsArray("projects", [mainProject])
+          time_entries: resultsObject([timeEntry])
+          projects: resultsObject([mainProject])
+      respondWith server, "/api/time_entries?include=task&only=#{timeEntry.id}&apply_default_filters=false",
+        resultsFrom: "time_entries"
+        data:
+          results: resultsArray("time_entries", [timeEntry])
+          time_entries: resultsObject([timeEntry])
+          tasks: resultsObject([timeTask])
+
+      loader = manager.loadModel "task", mainTask.id,
+        include: [
+          "assignees",
+          { sub_tasks: ["assignees"] },
+          { project: [{ time_entries: ["task"] }] }
+        ]
+
       model = loader.getModel()

-      server.respond()
+      server.respond() until server.queue.length == 0

       # check main model
       expect(model.attributes).toEqual(mainTask.attributes)
@@ -459,7 +486,9 @@
           collection.bind "loaded", checkStructure
           collection.bind "reset", checkStructure
           expect(success).not.toHaveBeenCalled()
-          server.respond()
+
+          server.respond() until server.queue.length == 0
+
           expect(success).toHaveBeenCalledWith(collection)
           expect(callCount).toEqual 3

@@ -816,6 +845,7 @@
         expect(successHandler).not.toHaveBeenCalled()
         expect(errorHandler).not.toHaveBeenCalled()
         server.respond()
+        server.respond()
         expect(successHandler).not.toHaveBeenCalled()
         expect(errorHandler).toHaveBeenCalled()
         expect(errorHandler.calls.count()).toEqual(1)


Fixes #415, fixes #688

Fixes #415, fixes #688

--- keymaps/git-plus.cson
+++ keymaps/git-plus.cson
@@ -10,24 +10,24 @@

 '.platform-darwin':
   'cmd-shift-h': 'git-plus:menu'
-  'cmd-shift-c': 'git-plus:commit'
-  'cmd-shift-a s': 'git-plus:status'
-  'cmd-shift-a q': 'git-plus:add-and-commit-and-push'
-  'cmd-shift-a a': 'git-plus:add-all-and-commit'
-  'cmd-shift-a p': 'git-plus:add-all-commit-and-push'
+  # 'cmd-shift-c': 'git-plus:commit'
+  # 'cmd-shift-a s': 'git-plus:status'
+  # 'cmd-shift-a q': 'git-plus:add-and-commit-and-push'
+  # 'cmd-shift-a a': 'git-plus:add-all-and-commit'
+  # 'cmd-shift-a p': 'git-plus:add-all-commit-and-push'

 '.platform-win32, .platform-linux':
   'ctrl-shift-h': 'git-plus:menu'
-  'ctrl-shift-x': 'git-plus:commit'
-  'ctrl-shift-a s': 'git-plus:status'
-  'ctrl-shift-a q': 'git-plus:add-and-commit-and-push'
-  'ctrl-shift-a a': 'git-plus:add-all-and-commit'
-  'ctrl-shift-a p': 'git-plus:add-all-commit-and-push'
+  # 'ctrl-shift-x': 'git-plus:commit'
+  # 'ctrl-shift-a s': 'git-plus:status'
+  # 'ctrl-shift-a q': 'git-plus:add-and-commit-and-push'
+  # 'ctrl-shift-a a': 'git-plus:add-all-and-commit'
+  # 'ctrl-shift-a p': 'git-plus:add-all-commit-and-push'

-'.platform-darwin atom-text-editor':
-  'cmd-shift-a': 'git-plus:add'
-  'cmd-shift-a c': 'git-plus:add-and-commit'
-
-'.platform-win32 atom-text-editor, .platform-linux atom-text-editor':
-  'ctrl-shift-a': 'git-plus:add'
-  'ctrl-shift-a c': 'git-plus:add-and-commit'
+# '.platform-darwin atom-text-editor':
+#   'cmd-shift-a': 'git-plus:add'
+#   'cmd-shift-a c': 'git-plus:add-and-commit'
+#
+# '.platform-win32 atom-text-editor, .platform-linux atom-text-editor':
+#   'ctrl-shift-a': 'git-plus:add'
+#   'ctrl-shift-a c': 'git-plus:add-and-commit'


jsonglue for clientside

jsonglue for clientside

--- grunt/options/coffee.coffee
+++ grunt/options/coffee.coffee
@@ -3,9 +3,15 @@
     bare: yes

   files: [
-    expand: true
-    cwd: "client/app/"
-    src: "**/*.coffee"
-    dest: "tmp/javascript/app"
-    ext: ".js"
+    {
+      expand: true
+      cwd: "client/app/"
+      src: "**/*.coffee"
+      dest: "tmp/javascript/app"
+      ext: ".js"
+    }
+    {
+      src: "server/utils/jsonglue.coffee"
+      dest: "tmp/javascript/app/vendor/jsonglue.js"
+    }
   ]


Set up stubbed clients

Set up stubbed clients

--- src/server/database/ClientService.coffee
+++ src/server/database/ClientService.coffee
@@ -1,10 +1,16 @@
 clients = [
 	_ =
 		id: "1"
-		name: "Samplr"
-		clientID: "abc123"
-		clientSecret: "ssh-secret"
-		redirectURI: "http://localhost:5000/cb"
+		name: "ton-hub"
+		clientID: "ton-hub"
+		clientSecret: "secret"
+		redirectURI: "http://localhost:5000/callback"
+	_ =
+		id: "2"
+		name: "Postman"
+		clientID: "postman"
+		clientSecret: "secret"
+		redirectURI: "https://www.getpostman.com/oauth2/callback"
 ]

 # TODO Obviously stubbed.