fix animations when paused

fix animations when paused

--- src/Main.coffee
+++ src/Main.coffee
@@ -15,6 +15,7 @@

   init: ->
     @game.physics.startSystem(Phaser.Physics.ARCADE)
+    @game.stage.disableVisibilityChange = true
     @generator = new Phaser.RandomDataGenerator([(new Date()).getTime()])

     @game.input.gamepad.start
@@ -50,6 +51,7 @@
     @dudes = @game.add.group()
     @avatar = @generator.pick(['nigel','bruce', 'julie', 'rachel'])
     @player = @addDude(@myId, 400, 225, @avatar)
+    @game.physics.arcade.enable(@player)

     @allPeers = [ ]
     @myPeerId = null
@@ -64,14 +66,14 @@
           channel = @peer.connect(peerId)
           channel.on 'open', =>
             console.info('Joining peer ' + peerId)
-            channel.send({ message: "arrive", x: @player.body.x, y: @player.body.y, @avatar })
+            channel.send({ message: "arrive" })
+            channel.send({ message: "initEntity", x: @player.body.x, y: @player.body.y, @avatar })
           channel
         console.log('all peers: ' + _.map @allPeers, (channel)->channel.peer )
     @peer.on 'connection', (remote)=>
       remote.on 'data', (data)=>
         if data.message == "arrive"
           console.info('Remote peer has arrived ' + remote.peer)
-          @addDude(remote.peer, data.x, data.y, data.avatar)
           channel = @peer.connect remote.peer
           channel.on 'open', =>
             console.info('Initializing ourselves on peer ' + remote.peer)
@@ -89,14 +91,12 @@

   addDude:(dudeId, x, y, sprite)->
     dude = @dudes.create(x, y, sprite)
-    dude.animations.frame = 1
     dude.animations.add("down", [0, 1, 2, 1], 20, true)
     dude.animations.add("left", [4, 5, 6, 5], 10, true)
     dude.animations.add("right", [8, 9, 10, 9], 10, true)
     dude.animations.add("up", [12, 13, 14, 13], 20, true)
     dude.animations.add("idle", [1], 20, true)
     @dudesById[dudeId] = dude
-    @game.physics.arcade.enable(dude)
     dude

   removeDude:(dudeId)->
@@ -110,7 +110,8 @@
     return if _.isUndefined(dude)
     dude.position.x = x
     dude.position.y = y
-    dude.animations.play(anim)
+    if dude.animations.currentAnim.name != anim
+      dude.animations.play(anim)

   update:->
     moves = @pollController()
@@ -134,7 +135,8 @@
         anim = "down"
       else if @player.body.velocity.y < -25
         anim = "up"
-    @player.animations.play(anim)
+    if @player.animations.currentAnim.name != anim
+      @player.animations.play(anim)

     @player.body.velocity.x *= 0.9
     @player.body.velocity.y *= 0.9


Pump: fixed performance issues with _pump method

The Promise.race resulted in high memory consumption, so it was removed
by making the current read cancellable.

--- src/Pump.coffee
+++ src/Pump.coffee
@@ -30,15 +30,10 @@

     @_from.on 'end', => do @sourceEnded

-    @bufferEndedWhileWaitingForRead = new Promise ->
-    @bufferEndedWhileWaitingForRead
-      .cancellable()
-      .catch(Promise.CancellationError, ->)
-
     @

   sourceEnded: ->
-    @bufferEndedWhileWaitingForRead.cancel()
+    @currentRead.cancel() if @currentRead
     @_state = Pump.SOURCE_ENDED

   buffers: (buffers = null) ->
@@ -61,11 +56,11 @@
   _pump: ->
     return do @subscribeForOutputBufferEnds if @_state == Pump.SOURCE_ENDED

-    Promise.race([
-      @_from.readAsync()
-      @bufferEndedWhileWaitingForRead
-    ])
-      .then (data) => @_process data
+    (@currentRead = @_from.readAsync())
+      .cancellable()
+      .then (data) =>
+        @currentRead = null
+        @_process data
       .catch(Promise.CancellationError, ->)
       .done => do @_pump


Add shims for previous fs methods

Add shims for previous fs methods

--- src/atom.coffee
+++ src/atom.coffee
@@ -3,6 +3,14 @@
 Emitter::one = (args...) -> @once(args...)
 Emitter::trigger = (args...) -> @emit(args...)
 Emitter::subscriptionCount = (args...) -> @getSubscriptionCount(args...)
+
+#TODO remove once all packages have been updated
+fs = require 'fs-plus'
+fs.exist = fs.existsSync
+fs.makeTree = fs.makeTreeSync
+fs.move = fs.moveSync
+fs.read = fs.readSync
+fs.remove = fs.removeSync

 fs = require 'fs-plus'
 {$} = require './space-pen-extensions'


fix(Sentry 3228): Bad math sending an incomplete bounds struct to Electron

--- src/atom.coffee
+++ src/atom.coffee
@@ -430,10 +430,12 @@

     startTime = Date.now()
     boundsForI = (i) ->
-      x: Math.round(startBounds.x + (width-startBounds.width) * -0.5 * i)
-      y: Math.round(startBounds.y + (height-startBounds.height) * -0.5 * i)
-      width: Math.round(startBounds.width + (width-startBounds.width) * i)
-      height: Math.round(startBounds.height + (height-startBounds.height) * i)
+      # It's very important this function never return undefined for any of the
+      # keys which blows up setBounds.
+      x: Math.round(startBounds.x + (width-startBounds.width) * -0.5 * i) ? 0
+      y: Math.round(startBounds.y + (height-startBounds.height) * -0.5 * i) ? 0
+      width: Math.round(startBounds.width + (width-startBounds.width) * i) ? width
+      height: Math.round(startBounds.height + (height-startBounds.height) * i) ? height

     tick = ->
       t = Math.min(1, (Date.now() - startTime) / (duration))


:penguin: Catch library errors requiring keytar

Refs atom/atom#4114

--- src/auth.coffee
+++ src/auth.coffee
@@ -1,4 +1,13 @@
-keytar = require 'keytar'
+try
+  keytar = require 'keytar'
+catch error
+  # Gracefully handle keytar failing to load due to missing library on Linux
+  if process.platform is 'linux'
+    keytar =
+      findPassword: ->
+      replacePassword: ->
+  else
+    throw error

 tokenName = 'Atom.io API Token'


Return pre-defined dkLen length

Return pre-defined dkLen length

--- src/auth.coffee
+++ src/auth.coffee
@@ -86,7 +86,7 @@
         prf: 'HMAC-SHA1'
         itrs: itrs
         salt: salt
-        dkLen: 256
+        dkLen: dkLen
         hash: hash

 module.exports = TurbasenAuth


remove mime dependency

remove mime dependency

--- src/blog.coffee
+++ src/blog.coffee
@@ -1,6 +1,5 @@
 {Promise} = require 'q'
 fs = require 'fs'
-mime = require 'mime'
 oauth = require 'oauth'
 request = require 'request'
 wsse = require 'wsse'
@@ -230,6 +229,4 @@

   _rawRequest: request

-  _mime: mime
-
 module.exports = Blog


remove redundant trace

remove redundant trace

--- server/gitrt.coffee
+++ server/gitrt.coffee
@@ -80,7 +80,6 @@
   Meteor.methods(
     sendOperation: (filePath, revision, operation, selection) ->
       try
-        console.log ['sendOperation', arguments...]
         server = editorServers[filePath]
         throw new Error("no server for #{filePath}") unless server


Resurrect single line comment languages.

--- src/lib/edify_.coffee
+++ src/lib/edify_.coffee
@@ -87,8 +87,10 @@
   parse: (options...) -> @contents.push options
   language: (language, options) ->
     if typeof options.docco is "string"
-      options.docco = new RegExp "^\\s*#{options.docco}\\s*(.*)"
-      options.divider = "# --- EDIFY DIVIDER ---"
+      comment = options.docco
+      options.docco = {}
+      options.docco.start = new RegExp "^\\s*#{comment}\\s*(.*)$"
+      options.divider = "#{comment} --- EDIFY DIVIDER ---"
     @languages[language] = options
   stencil: (regex, stencil) ->
     @templates.push { regex, stencil }
@@ -161,17 +163,24 @@
       else
         for line in lines
           if docco
-            if match = language.docco.end.exec line
-              if match[1].trim()
-                page[0].docco.push match[1].replace language.docco.strip, ""
+            if language.docco.end
+              if match = language.docco.end.exec line
+                if match[1].trim()
+                  page[0].docco.push match[1].replace language.docco.strip, ""
+                docco = false
+              else
+                page[0].docco.push line.replace language.docco.strip, ""
+            else if match = language.docco.start.exec line
+              page[0].docco.push match[1]
+            else
               docco = false
-            else
-              page[0].docco.push line.replace language.docco.strip, ""
+              page[0].source = [] unless page[0].source
+              page[0].source.push line
           else if match = language.docco.start.exec line
             docco = true
             page.unshift { docco: [] } if page[0].source
             comment = match[1]
-            if match = language.docco.end.exec comment
+            if language.docco.end and match = language.docco.end.exec comment
               comment = match[1]
               docco = false
             page[0].docco.push comment


Fix for change in data returned by intel map API

--- src/lib/entity.coffee
+++ src/lib/entity.coffee
@@ -1,91 +1,94 @@
 async = require 'async'
 requestFactory = require './requestfactory.js'
 request = requestFactory()
-
+
 requested_guid = {}
-
-Entity = GLOBAL.Entity =
-
-    counter:
+
+Entity = GLOBAL.Entity =
+
+    counter:
         portals: 0
         fields:  0
         links:   0
-
+
     entityCount: 0
-
+
     add: (id, timestamp, data, callback) ->
-
+        data = remap data
         # update counter every 100 entities
-
+
         main = ->
-
+
             Entity.entityCount++
-
+
             if data.type is 'portal'
                 createPortalEntity id, timestamp, data, callback
             else if data.type is 'region'
-                createFieldEntity id, timestamp, data, callback
+                #createFieldEntity id, timestamp, data, callback
+                return callback()
             else if data.type is 'edge'
-                createLinkEntity id, timestamp, data, callback
+                #createLinkEntity id, timestamp, data, callback
+                return callback()
             else
                 logger.warn "[Entity] Unknown entity type, id=#{id}, type=#{data.type}"
                 callback && callback()
-
-
+
+
         if Entity.entityCount % 100 is 0
-
+
             async.parallel [
-
+
                 (callback) ->
-
+
                     Database.db.collection('Portals').count {}, (err, count) ->
                         Entity.counter.portals = count if not err
                         callback()
-
+
                 (callback) ->
-
+
                     Database.db.collection('Fields').count {}, (err, count) ->
                         Entity.counter.fields = count if not err
                         callback()
-
+
                 (callback) ->
-
+
                     Database.db.collection('Links').count {}, (err, count) ->
                         Entity.counter.links = count if not err
                         callback()
-
+
             ], main
-
+
         else
-
+
             main()
-
+
     requestPortalDetail: (guid, outerCallback) ->
-
+
         # TODO: WTF?
         return outerCallback() if requested_guid[guid]?
-
+
         requested_guid[guid] = true
-
+
         t = 0
-
+
         request.push
-
+
             action: 'getPortalDetails'
             data:
                 guid: guid
             beforeRequest: (callback) ->
-
+
                 t = Date.now()
                 callback()
-
+
             onSuccess: (response, callback) ->
-
+                response = remap response.result
+
                 if response.capturedTime?
                     response.capturedTime = parseInt response.capturedTime
-
+
                 return callback() if ig.indexOf(response.owner) > -1
-
+
                 Database.db.collection('Portals').findAndModify
                     _id: guid   #query
                 ,
@@ -95,31 +98,31 @@
                 ,
                     new: true   # options
                 , (err, data) ->
-
+
                     if err
                         logger.error '[Details] Failed to update portal detail (guid=%s) in database: %s', guid, err.message
-
+
                     # resolve agent information
                     Agent.resolveFromPortalDetail data, callback
-
+
             onError: (err, callback) ->
-
+
                 logger.error "[Details] #{err.message}"
                 callback()
-
+
             afterResponse: (callback) ->
-
+
                 logger.info "[Details] " +
                     Math.round(request.done / request.max * 100).toString() +
                     "%\t[#{request.done}/#{request.max}]\t#{Date.now() - t}ms"
-
+
                 callback()
                 outerCallback()
-
+
     requestMissingPortals: (callback) ->
-
+
         # request missing portal details
-
+
         Database.db.collection('Portals').find(
             team:
                 $ne: 'NEUTRAL'
@@ -128,22 +131,22 @@
         ,
             _id: true
         ).toArray (err, portals) ->
-
+
             if err
                 logger.error '[Details] Failed to fetch missing portal list: %s', err.message
                 return callback()
-
+
             if portals
                 async.each portals, (po, callback) ->
                     Entity.requestPortalDetail po._id, callback
                 , callback
             else
                 callback()
-
+
 createEntity = (collection, id, timestamp, data, callback) ->
-
+
     data.time = timestamp
-
+
     Database.db.collection(collection).update
         _id: id
     ,
@@ -152,33 +155,87 @@
     ,
         upsert: true
     , (err) ->
-
+
         if err
             logger.error '[Entity] Failed to insert entity (id=%s) into database: %s', id, err.message
-
+
         # ignore error
         callback()
-
+
 createPortalEntity = (id, timestamp, data, callback) ->
-
+
     data.pos =
         lat: data.latE6 / 1e6
         lng: data.lngE6 / 1e6
-
+
     createEntity 'Portals', id, timestamp, data, ->
-
+
         if data.team isnt 'NEUTRAL' and argv.detail isnt 'false'
             Entity.requestPortalDetail id, ->
                 callback && callback 'portal'
         else
             callback && callback 'portal'
-
+
 createFieldEntity = (id, timestamp, data, callback) ->
-
+
     createEntity 'Fields', id, timestamp, data, ->
         callback && callback 'field'
-
+
 createLinkEntity = (id, timestamp, data, callback) ->
-
+
     createEntity 'Links', id, timestamp, data, ->
         callback && callback 'link'
+
+
+types = {
+    'p' : 'portal'
+    'r' : 'region'
+    'e' : 'edge'
+}
+
+teams = {
+    'E' : 'ENLIGHTENED'
+    'R' : 'RESISTANCE'
+    'N' : 'NEUTRAL'
+}
+
+remap = (data) ->
+#TODO: links and fields
+    result = {
+        type: types[data[0]]
+        team: teams[data[1]]
+        latE6: data[2]
+        lngE6: data[3]
+        level: data[4]
+        health: data[5]
+        resCount: data[6]
+        image: data[7]
+        title: data[8]
+        ornaments: data[9]
+    }
+    result.mods = remapMods(data[10]) if data[10]
+    result.resonators = remapResos(data[11]) if data[11]
+    result.owner = data[12] if data[12]
+    return result
+
+
+remapMods = (mods) ->
+    result = []
+    for mod in mods
+        result.push(if mod then {
+            owner: mod[0]
+            name: mod[1]
+            rarity: mod[2]
+            stats: mod[3]
+        } else null);
+    return result;
+
+remapResos = (resos) ->
+    result = []
+    for reso in resos
+        result.push(if reso then {
+            owner: reso[0]
+            level: reso[1]
+            energy: reso[2]
+        } else null);
+    return result;


Revert "change"

Revert "change"

This reverts commit 104ce81ebdf41d88acd7fb6f2abf99fbc4eb91df.

--- services/clsi/app.coffee
+++ services/clsi/app.coffee
@@ -1,4 +1,3 @@
-
 CompileController = require "./app/js/CompileController"
 Settings = require "settings-sharelatex"
 logger = require "logger-sharelatex"


be ready to serve files from per-user containers

--- services/clsi/app.coffee
+++ services/clsi/app.coffee
@@ -42,6 +42,12 @@
 	else
 		next new Error("invalid project id")

+app.param 'user_id', (req, res, next, project_id) ->
+	if project_id?.match /^[a-zA-Z0-9_-]+$/
+		next()
+	else
+		next new Error("invalid user id")
+
 app.param 'build_id', (req, res, next, build_id) ->
 	if build_id?.match OutputCacheManager.BUILD_REGEX
 		next()
@@ -71,6 +77,11 @@
 			'-' + Number(stat.size).toString(16) + '"'
 		res.set("Etag", etag(path, stat))
 	res.set("Content-Type", ContentTypeMapper.map(path))
+
+app.get "/project/:project_id/user/:user_id/build/:build_id/output/*", (req, res, next) ->
+	# for specific build get the path from the OutputCacheManager (e.g. .clsi/buildId)
+	req.url = "/#{req.params.project_id}-#{req.params.user_id}/" + OutputCacheManager.path(req.params.build_id, "/#{req.params[0]}")
+	staticServer(req, res, next)

 app.get "/project/:project_id/build/:build_id/output/*", (req, res, next) ->
 	# for specific build get the path from the OutputCacheManager (e.g. .clsi/buildId)


Use old PLUGIN_METHOD_ATTACH for webview

--- atom/renderer/lib/webview.coffee
+++ atom/renderer/lib/webview.coffee
@@ -5,6 +5,10 @@
 # ID generator.
 nextId = 1
 getNextId = -> nextId++
+
+# FIXME
+# Discarded after Chrome 39
+PLUGIN_METHOD_ATTACH = '-internal-attach'

 # Attributes.
 WEB_VIEW_ATTRIBUTE_ALLOWTRANSPARENCY = 'allowtransparency'
@@ -283,11 +287,13 @@
       @partition.fromAttribute newValue, @hasNavigated()

   handleBrowserPluginAttributeMutation: (name, oldValue, newValue) ->
-    if name is 'internalinstanceid' and !oldValue and !!newValue
-      @browserPluginNode.removeAttribute 'internalinstanceid'
-      @internalInstanceId = parseInt newValue
-
-      console.log 'internalinstanceid', @internalInstanceId
+    # FIXME
+    # internalbindings => internalInstanceid after Chrome 39
+    if name is 'internalbindings' and !oldValue and !!newValue
+      @browserPluginNode.removeAttribute 'internalbindings'
+      # FIXME
+      # @internalInstanceId = parseInt newValue
+
       if !!@guestInstanceId and @guestInstanceId != 0
         isNewWindow = if @deferredAttachState then @deferredAttachState.isNewWindow else false
         params = @buildAttachParams isNewWindow
@@ -297,6 +303,7 @@
         #   @guestInstanceId,
         #   params,
         #   (w) => @contentWindow = w
+        @browserPluginNode[PLUGIN_METHOD_ATTACH] @guestInstanceId, params

   onSizeChanged: (webViewEvent) ->
     newWidth = webViewEvent.newWidth
@@ -348,7 +355,9 @@

   # Returns if <object> is in the render tree.
   isPluginInRenderTree: ->
-    !!@internalInstanceId && @internalInstanceId != 0
+    # FIXME
+    # !!@internalInstanceId && @internalInstanceId != 0
+    'function' == typeof this.browserPluginNode[PLUGIN_METHOD_ATTACH]

   hasNavigated: ->
     not @beforeFirstNavigation
@@ -458,6 +467,7 @@
     @deferredAttachState = null
     # FIXME
     # guestViewInternalNatives.AttachGuest @internalInstanceId, @guestInstanceId, params, (w) => @contentWindow = w
+    @browserPluginNode[PLUGIN_METHOD_ATTACH] @guestInstanceId, params

 # Registers browser plugin <object> custom element.
 registerBrowserPluginElement = ->


Remove debug code

Remove debug code

--- src/scripts/loader.coffee
+++ src/scripts/loader.coffee
@@ -23,8 +23,6 @@
         switch jqXHR.status
           when 403 # HACK: this should be 401
             window.location.href = '/login'
-          else
-            console.error(arguments)

     return promise


Fix 404 route

Fix 404 route

--- src/scripts/router.coffee
+++ src/scripts/router.coffee
@@ -10,7 +10,7 @@

       # Default Route
       @route '*actions', 'default', () ->
-        @appView.render('error', {error: 404, message: 'Page Not Found'})
+        @appView.render('error', {code: 404})

       @route '', 'index', () ->
         @appView.render('home')


Ship It Good

Ship It Good

--- src/scripts/shipit.coffee
+++ src/scripts/shipit.coffee
@@ -8,7 +8,8 @@
   "https://img.skitch.com/20111026-r2wsngtu4jftwxmsytdke6arwd.png",
   "http://images.cheezburger.com/completestore/2011/11/2/aa83c0c4-2123-4bd3-8097-966c9461b30c.jpg",
   "http://images.cheezburger.com/completestore/2011/11/2/46e81db3-bead-4e2e-a157-8edd0339192f.jpg",
-  "http://28.media.tumblr.com/tumblr_lybw63nzPp1r5bvcto1_500.jpg"
+  "http://28.media.tumblr.com/tumblr_lybw63nzPp1r5bvcto1_500.jpg",
+  "http://i.imgur.com/DPVM1.png"
 ]

 module.exports = (robot) ->


fixed bad icon reference of follower

fixed bad icon reference of follower

--- public/dreamcatcher/controllers/settings_controller.coffee
+++ public/dreamcatcher/controllers/settings_controller.coffee
@@ -18,14 +18,14 @@
     switch sharingLevel
       when 500 then background = 'sharing-24-hover.png'
       when 200 then background = 'friend-24.png'
-      when 150 then background = 'friend-follower-24.png'
+      when 150 then background = 'friend-24-follower.png'
       when 50 then background = 'anon-24-hover.png'
       when 0 then background = 'private-24-hover.png'

     $('.sharing-icon').css "background","url(/images/icons/#{background}) no-repeat center transparent"

   displayLandingPage: (landingPage) ->
-    #TODO: after SASS refactor, replace with class + Geoff please adjust the icon settings here
+    #TODO: after SASS refactor, replace with class
     switch landingPage
       when 'stream' then background = 'stream-24-hover.png'
       when 'home' then background = 'home-24-hover.png'


added initial default font size stuff to settings controller

--- public/dreamcatcher/controllers/settings_controller.coffee
+++ public/dreamcatcher/controllers/settings_controller.coffee
@@ -49,7 +49,12 @@
     pageBody = $('#body')
     pageBody.removeClass(className) for className in ['float','inpage']
     pageBody.addClass(defaultMenuStyle)
-
+
+  displayDefaultFontSize: (defaultFontSize) ->
+    body = $('#body')
+    body.removeClass(className) for className in ['small','medium','large']
+    body.addClass(defaultFontSize)
+    # when #fontLarge is clicked add class #fontLarge to body

   updateSettingsModel: (params) ->
     Dreamcatcher.Models.Settings.update params
@@ -92,6 +97,11 @@
     @displayDefaultMenuStyle defaultMenuStyle
     @updateSettingsModel {'user[default_menu_style]': defaultMenuStyle}

+  '#default-fontSize change': (element) ->
+    defaultFontSize = element.className
+    @displayDefaultFontSize defaultFontSize
+    @updateSettingsModel {'user[default_font_size]': defaultFontSize}
+
   '.cancel click': ->
     $('.changePasswordForm').hide()
     $('#user_password,#user_password_confirmation').val ''


Remove useless taiko stats

Remove useless taiko stats

--- resources/assets/coffee/react/beatmapset-page/stats.coffee
+++ resources/assets/coffee/react/beatmapset-page/stats.coffee
@@ -79,7 +79,12 @@
       div className: 'beatmapset-stats__row beatmapset-stats__row--advanced',
         table className: 'beatmap-stats-table',
           tbody null,
-            for stat in ['cs', 'drain', 'accuracy', 'ar', 'stars']
+            stats =
+              if @props.beatmap.mode == 'taiko'
+                ['drain', 'accuracy', 'stars']
+              else
+                ['cs', 'drain', 'accuracy', 'ar', 'stars']
+            for stat in stats
               value =
                 if stat == 'stars'
                   @props.beatmap.difficulty_rating


Use newBody for attaching portals

Use newBody for attaching portals

--- resources/assets/lib/components/notification-banner.coffee
+++ resources/assets/lib/components/notification-banner.coffee
@@ -44,4 +44,5 @@

   createPortalContainer: =>
     @portalContainer = document.createElement 'div'
-    notificationBanners[0].appendChild @portalContainer
+    notificationBanners = (window.newBody ? document.body).querySelector('.js-notification-banners')
+    notificationBanners.appendChild @portalContainer


remove unitary.coffee

remove unitary.coffee

--- unitary.coffee
+++ unitary.coffee
@@ -1,448 +0,0 @@
-"use strict"
-gcd = (m, n) -># {{{
-  if m < n
-    return gcd(n, m)
-  if m < 0
-    return gcd(-m, n)
-  if n < 0
-    return gcd(m, -n)
-  if n is 0
-    return m
-  return gcd(n, m % n)# }}}
-distance = (A, B) -># {{{
-  if (A instanceof Point and B instanceof Point)
-    return Math.sqrt((A.x - B.x) * (A.x - B.x) + (A.y - B.y) * (A.y - B.y))
-  if (A instanceof Point and B instanceof Line)
-    res = B.a * A.x + B.b * A.y + B.c
-    res *= -1 if res < 0
-    res /= Math.sqrt(B.a * B.a + B.b * B.b)
-    return res
-  if (A instanceof Line and B instanceof Point)
-    return distance(B, A)# }}}
-
-class UnitaryObject# {{{
-  constructor: () ->
-    if (not (@ instanceof UnitaryObject))
-      throw new Error('Constructor cannot be called as a function.')
-    @.fillColor = null
-    @.strokeColor = null
-  equals: (B) ->
-    return @.name() == B.name()
-  setFillColor: (color) ->
-    @.fillColor = color
-    return @
-  setStrokeColor: (color) ->
-    @.strokeColor = color
-    return @
-  moveX: (dx) ->
-    return @.move(dx, 0)
-  moveY: (dy) ->
-    return @.move(0, dy)
-  name: () -> 'UnitaryObject'# }}}
-class Point extends UnitaryObject# {{{
-  constructor: (x, y) ->
-    if (not (@ instanceof Point))
-      throw new Error('Constructor cannot be called as a function.')
-    @.x = x
-    @.y = y
-
-  moveTo: (x, y) ->
-    return new Point(x, y).setStrokeColor(@.strokeColor).setFillColor(@.fillColor)
-  move: (dx, dy) ->
-    return new Point(@.x + dx, @.y + dy).setStrokeColor(@.strokeColor).setFillColor(@.fillColor)
-  toString: () ->
-    return '(' + @.x + ', ' + @.y + ')'
-  inspect: () ->
-    return '(' + @.x + ', ' + @.y + ')'
-  equals: (B) ->
-    if (!super(B))
-      return false
-    return @.x == B.x && @.y == B.y
-  name: () -> 'Point'
-# }}}
-class Vector extends UnitaryObject# {{{
-  constructor: () ->
-    if (not (@ instanceof Vector))
-      throw new Error('Constructor cannot be called as a function.')
-    super()
-    if (arguments.length == 2)
-      @.x = arguments[0]
-      @.y = arguments[1]
-    else if (arguments.length == 1)
-      @.x = arguments[0].x
-      @.y = arguments[0].y
-  add: (CD) ->
-    return new Vector(@.x + CD.x, @.y + CD.y)
-  minus: (CD) ->
-    return new Vector(@.x - CD.x, @.y - CD.y)
-  product: (CD) ->
-    return @.x * CD.x + @.y * CD.y
-  multiple: (k) ->
-    return new Vector(@.x * k, @.y * k)
-  abs: () ->
-    return Math.sqrt(@.x ** 2 + @.y ** 2)
-  equals: (B) ->
-    if (!super(B))
-      return false
-    return @.x == B.x && @.y == B.y
-  move: (dx, dy) ->
-    return new Vector(@.x + dx, @.y + dy)
-  name: () -> 'Vector'
-# }}}
-class Line extends UnitaryObject# {{{
-  constructor: (A, B) ->
-    if (not (@ instanceof Line))
-      throw new Error('Constructor cannot be called as a function.')
-    if (A.equals(B))
-      throw new Error('A equals B. So AB couldn\'t construct line.')
-    super()
-    @.points = [A, B]
-    @.a = B.y - A.y
-    @.b = A.x - B.x
-    @.c = A.x * (A.y - B.y) - A.y * (A.x - B.x)
-    g = gcd(gcd(@.a, @.b), @.c)
-    @.a /= g
-    @.b /= g
-    @.c /= g
-    if (@.a == 0)
-      # @.b != 0
-      @.c /= @.b
-      @.b = 1
-    if (@.b == 0)
-      # @.a != 0
-      @.c /= @.a
-      @.a = 1
-  move: (dx, dy) ->
-    return new Line(@.points[0].move(dx, dy), @.points[1].move(dx, dy)).setStrokeColor(@.strokeColor).setFillColor(@.fillColor)
-  toString: () ->
-    res = ''
-    if (@.a > 0 and @.a isnt 1) then res += '+' + @.a + 'x'
-    if (@.a is 1) then res += '+x'
-    if (@.a < 0 and @.a isnt -1) then res += '-' + -@.a + 'x'
-    if (@.a is -1) then res += '-x'
-
-    if (@.b > 0 and @.b isnt 1) then res += '+' + @.b + 'y'
-    if (@.b is 1) then res += '+y'
-    if (@.b < 0 and @.b isnt -1) then res += '-' + -@.b + 'y'
-    if (@.b is -1) then res += '-y'
-
-    if (@.c > 0) then res += '+' + @.c
-    if (@.c < 0) then res += '-' + -@.c
-
-    if res.charAt(0) is '+' then res = res.slice(1)
-
-    return res + '=0'
-  inspect: () ->
-    res = ''
-    if (@.a > 0 and @.a isnt 1) then res += '+' + @.a + 'x'
-    if (@.a is 1) then res += '+x'
-    if (@.a < 0 and @.a isnt -1) then res += '-' + -@.a + 'x'
-    if (@.a is -1) then res += '-x'
-
-    if (@.b > 0 and @.b isnt 1) then res += '+' + @.b + 'y'
-    if (@.b is 1) then res += '+y'
-    if (@.b < 0 and @.b isnt -1) then res += '-' + -@.b + 'y'
-    if (@.b is -1) then res += '-y'
-
-    if (@.c > 0) then res += '+' + @.c
-    if (@.c < 0) then res += '-' + -@.c
-
-    if res.charAt(0) is '+' then res = res.slice(1)
-
-    return res + '=0'
-  getIntersection: (CD) ->
-    if (@.a == CD.a && @.b == CD.b)
-      return false
-    y = (CD.a * @.c - @.a * CD.c) / (@.a * CD.b - CD.a * @.b)
-    x = -1 * (@.b * y + @.c) / (@.a)
-    return new Point(x, y)
-  equals: (CD) ->
-    if (!super(CD))
-      return false
-    return @.a == CD.a && @.b == CD.b && @.c == CD.c
-  name: () -> 'Line'
-# }}}
-class Segment extends UnitaryObject# {{{
-  constructor: (A, B) ->
-    if (not (@ instanceof Segment))
-      throw new Error('Constructor cannot be called as a function.')
-    if A.x > B.x
-      @.points = [B, A]
-    else
-      @.points = [A, B]
-    super()
-    @.length = Math.sqrt((A.x - B.x) ** 2 + (A.y - B.y) ** 2)
-  move: (dx, dy) ->
-    return new Segment(@.points[0].move(dx, dy), @.points[1].move(dx, dy)).setStrokeColor(@.strokeColor).setFillColor(@.fillColor)
-  has: (P) ->
-    A = @.points[0]
-    B = @.points[1]
-    if A.x <= P.x <= B.x
-      if A.y <= B.y and A.y <= P.y <= B.y or A.y >= B.y and A.y >= P.y >= B.y
-        if (A.y - B.y) / (A.x - B.x) * P.x is P.y
-          return true
-    return false
-  intersects: (CD) ->
-    intersection = @.toLine().getIntersection(CD.toLine())
-    if (intersection == false)
-      return false
-    if (@.points[0].x <= intersection.x <= @.points[1].x)
-      return true
-    return false
-  toLine: () ->
-    return new Line(@.points[0], @.points[1])
-  equals: (CD) ->
-    if (!super(CD))
-      return false
-    return @.points[0].equals(CD.points[0]) && @.points[1].equals(CD.points[1])
-  name: () -> 'Segment'
-# }}}
-class Circle extends UnitaryObject# {{{
-  constructor: (O, radius) ->
-    if (not (@ instanceof Circle))
-      throw new Error('Constructor cannot be called as a function.')
-    super()
-    @.Origin = O
-    @.r = radius
-  moveTo: (x, y) ->
-    return new Circle(@.Origin.moveTo(x, y), @.r).setStrokeColor(@.strokeColor).setFillColor(@.fillColor)
-  move: (dx, dy) ->
-    return new Circle(@.Origin.move(dx, dy), @.r).setStrokeColor(@.strokeColor).setFillColor(@.fillColor)
-  equals: (C) ->
-    if (!super(C))
-      return false
-    return @.Origin.equals(C.Origin) && @.r == C.r
-  name: () -> 'Circle'
-# }}}
-class Polygon extends UnitaryObject# {{{
-  constructor: (points...) ->
-    if (not (@ instanceof Polygon))
-      throw new Error('Constructor cannot be called as a function.')
-    super()
-    if (Object.prototype.toString.call(points[0]) == '[object Array]')
-      @.points = points[0]
-    else
-      @.points = points
-  equals: () -> false
-  move: (dx, dy) ->
-    points = []
-    length = 0
-    for val in @.points
-      points[length] = val.move(dx, dy)
-      length = 0|length+1
-    return new Polygon(points).setStrokeColor(@.strokeColor).setFillColor(@.fillColor)
-  has: (P) ->
-    before_v = @.points[@.points.length - 1]
-    rad = 0
-    for v in @.points
-      a = new Vector(v).minus(new Vector(P))
-      b = new Vector(before_v).minus(new Vector(P))
-      cos = a.product(b) / (a.abs() * b.abs())
-      rad += Math.acos(cos)
-      before_v = v
-    return Math.round(rad / (2 * Math.PI) * 360) == 360
-  name: () -> 'Polygon'
-# }}}
-class Quadrilateral extends Polygon# {{{
-  constructor: (A, B, C, D) ->
-    if (not (@ instanceof Quadrilateral))
-      throw new Error('Constructor cannot be called as a function.')
-    if (new Segment(A, D).intersects(new Segment(B, C)))
-      throw new Error('ABCD is not a quadrilateral.')
-    if (A.equals(B) || A.equals(C) || A.equals(D) || B.equals(C) || B.equals(D) || C.equals(D))
-      throw new Error('ABCD is not a quadrilateral.')
-    super(A, B, C, D)
-  getArea: () ->
-    [A, B, C, D] = @.points
-    S1 = new Triangle(A, B, C).getArea()
-    S2 = new Triangle(A, C, D).getArea()
-    return S1 + S2
-  name: () -> 'Quadrilateral'
-# }}}
-class Triangle extends Polygon# {{{
-  constructor: (A, B, C) ->
-    if (not (@ instanceof Triangle))
-      throw new Error('Constructor cannot be called as a function.')
-    if (not A? || not B? || not C?)
-      throw new Error('Triangle must have three vertices.')
-    if (A.equals(B) || B.equals(C) || A.equals(C))
-      throw new Error('Triangle must have three vertices.')
-    super(A, B, C)
-  getCircumcircle: () ->
-    A = @.points[0]
-    B = @.points[1]
-    C = @.points[2]
-    AB = new Segment(A, B)
-    BC = new Segment(B, C)
-    CA = new Segment(C, A)
-    S = @.getArea()
-    vA = new Vector(A.x, A.y)
-    vB = new Vector(B.x, B.y)
-    vC = new Vector(C.x, C.y)
-    a = BC.length ** 2
-    b = CA.length ** 2
-    c = AB.length ** 2
-    vO = new Vector(0,0)
-      .add(vA.multiple(a * (b + c - a)))
-      .add(vB.multiple(b * (c + a - b)))
-      .add(vC.multiple(c * (a + b - c)))
-      .multiple(1 / (16 * (S ** 2)))
-    O = new Point(vO.x, vO.y)
-    cosA = vB.minus(vA).product(vC.minus(vA)) / (AB.length * CA.length)
-    sinA = Math.sqrt(1 - cosA ** 2)
-    R = BC.length / sinA / 2
-    return new Circle(O, R)
-  getIncircle: () ->
-    vA = new Vector(@.points[0].x, @.points[0].y)
-    vB = new Vector(@.points[1].x, @.points[1].y)
-    vC = new Vector(@.points[2].x, @.points[2].y)
-    a = vC.minus(vB).abs()
-    b = vC.minus(vA).abs()
-    c = vB.minus(vA).abs()
-    vO = new Vector(0, 0)
-      .add(vA.multiple(a / (a + b + c)))
-      .add(vB.multiple(b / (a + b + c)))
-      .add(vC.multiple(c / (a + b + c)))
-    O = new Point(vO.x, vO.y)
-    r = 2 * @.getArea() / (a + b + c)
-    return new Circle(O, r)
-  getArea: () ->
-    A = @.points[0]
-    B = @.points[1]
-    C = @.points[2]
-    AB = new Segment(A, B)
-    AC = new Segment(A, C)
-    vAB = new Vector(B.x - A.x, B.y - A.y)
-    vAC = new Vector(C.x - A.x, C.y - A.y)
-    cosA = vAB.product(vAC) / (AB.length * AC.length)
-    sinA = Math.sqrt(1 - cosA ** 2)
-    S = AB.length * AC.length * sinA / 2
-    return S
-  name: () -> 'Triangle'
-# }}}
-class Rect extends Polygon# {{{
-  constructor: (A, B) ->
-    if (not (@ instanceof Rect))
-      throw new Error('Constructor cannot be called as a function.')
-    super(A, B)
-  has: (P) ->
-    A = @.points[0]
-    B = @.points[1]
-    return (A.x - P.x) * (B.x - P.x) <= 0 && (A.y - P.y) * (B.y - P.y) <= 0
-  name: () -> 'Rect'
-# }}}
-class Text_ extends UnitaryObject# {{{
-  # Text コンストラクタが既にブラウザに存在しているためText_とした
-  constructor: (str, P, align = 'left', maxWidth = null) ->
-    if (not (@ instanceof Text_))
-      throw new Error('Constructor cannot be called as a function.')
-    @.P = P
-    @.string = str
-    @.text = str
-    @.align = align
-    @.maxWidth = maxWidth
-    @.strokesOutline = false
-    @.fillColor = '#000'
-    @.outlineColor = '#000'
-    @.baseline = 'alphabetic'
-    @.font = null
-  strokeOutline: () ->
-    @.strokesOutline = true
-    return @
-  setAlign: (align) ->
-    @.align = align
-    return @
-  setOutlineColor: (color) ->
-    @.outlineColor = color
-    return @
-  setBaseline: (base) ->
-    @.baseline = base
-    return @
-  setFont: (font) ->
-    @.font = font
-    return @
-  move: (dx, dy) ->
-    return new Text_(@.str, @.P.move(dx, dy), @.align, @.maxWidth).setStrokeColor(@.strokeColor).setFillColor(@.fillColor).setOutlineColor(@.outlineColor).setBaseline(@.baseline).setFont(@.font)
-  name: () -> 'Text'
-# }}}
-class Image_ extends UnitaryObject# {{{
-  # Imageコンストラクタが既にブラウザに存在しているためImage_とした
-  constructor: (src, startPoint) ->
-    if (not (@ instanceof Image_))
-      throw new Error('Constructor cannot be called as a function.')
-    @.src = src
-    @.startPoint = startPoint
-    @.dx = startPoint.x
-    @.dy = startPoint.y
-    @.dw = null
-    @.dh = null
-    @.sw = null
-    @.sh = null
-    @.sx = null
-    @.sy = null
-  trim: (startPoint, sw, sh, dw, dh) ->
-    if (not dw?)
-      dw = sw
-    if (not dh?)
-      dh = sh
-    @.sx = startPoint.x
-    @.sy = startPoint.y
-    @.sw = sw
-    @.sh = sh
-    @.dw = dw
-    @.dh = dh
-
-  resize: (dw, dh) ->
-    @.dw = dw
-    @.dh = dh
-  equals: (B) ->
-    if (!super(B))
-      return false
-    return @.src == B.src && @.dx == B.dx && @.dy == B.dy && @.dw == B.dw && @.dh == B.dh && @.sw == B.sw && @.sh == B.sh && @.sx == B.sx && @.sy == B.sy
-  move: (dx, dy) ->
-    newImage = new Image_(@.src, @.startPoint.move(dx, dy))
-    if (@.sx != null)
-      newImage.trim(new Point(@.sx, @.sy), @.sw, @.sh, @.dw, @.dh)
-    return newImage
-  name: () -> 'Image'
-# }}}
-class Graph extends UnitaryObject# {{{
-  constructor: (f, scale) ->
-    super()
-    @.f = f
-    @.scale = scale
-    @.start = null
-    @.end = null
-  setRange: (start, end) ->
-    @.start = start
-    @.end = end
-    return @
-  equals: () -> false
-  moveX: undefined
-  moveY: undefined
-  name: () -> 'Graph'# }}}
-XAxis = new Line(new Point(0, 0), new Point(1, 0))
-YAxis = new Line(new Point(0, 0), new Point(0, 1))
-
-_global = (@ || 0).self || global
-Module = {
-  UnitaryObject: UnitaryObject
-  Circle: Circle
-  Graph: Graph
-  Image: Image_
-  Line: Line
-  Point: Point
-  Polygon: Polygon
-  Quadrilateral: Quadrilateral
-  Rect: Rect
-  Segment: Segment
-  Text: Text_
-  Triangle: Triangle
-  Vector: Vector
-  XAxis:XAxis
-  YAxis:YAxis
-  distance: distance
-}
-if ('process' of _global)
-  module.exports = Module
-_global['Unitary'] = Module


automatic uuid option enabled

automatic uuid option enabled

--- server/src/Component/Builtin/Database/MyNinja.coffee
+++ server/src/Component/Builtin/Database/MyNinja.coffee
@@ -189,11 +189,14 @@
                     original.info = results
                     return callback(null, original)
             else
-                data.id = @$.escape(uuid.v1()) if params.uuid
+                id = null
+                if params.uuid
+                    id = uuid.v1()
+                    data.id = @$.escape(id)
                 sql = @$.insertInto(@_table).set(data).build()
                 @_mysql.query sql, [], @_dataSourceName, (error, results) =>
                     return callback(error) if error
-                    original[@_primaryKey] = results.insertId
+                    original[@_primaryKey] = id || results.insertId
                     return callback(null, original)

         if validate and @_validate?


Looped over titles and added them to list with all titles

--- source/Downloader.coffee
+++ source/Downloader.coffee
@@ -68,3 +68,4 @@
     downloads = []
     winston.info('Fetched links.')
     $('.item').find('.desc').each (i, element) ->
+      titles.push($(this).text().trim())


group: member permission edit item view, click events would propagate and open new dashboard

--- client/app/Applications/Groups.kdapplication/Views/groupsmemberpermissionslistitemview.coffee
+++ client/app/Applications/Groups.kdapplication/Views/groupsmemberpermissionslistitemview.coffee
@@ -23,14 +23,19 @@
       cssClass         : 'fr edit-link'
       icon             :
         cssClass       : 'edit'
-      click            : @bound 'showEditMemberRolesView'
+      click            : (event)=>
+        event.stopPropagation()
+        event.preventDefault()
+        @showEditMemberRolesView()

     @saveLink          = new CustomLinkView
       title            : 'Save'
       cssClass         : 'fr hidden save-link'
       icon             :
         cssClass       : 'save'
-      click            : =>
+      click            : (event)=>
+        event.stopPropagation()
+        event.preventDefault()
         @emit 'RolesChanged', @getData(), @editView.getSelectedRoles()
         @hideEditMemberRolesView()
         log "save"
@@ -40,7 +45,10 @@
       cssClass         : 'fr hidden cancel-link'
       icon             :
         cssClass       : 'delete'
-      click            : @bound 'hideEditMemberRolesView'
+      click            : (event)=>
+        event.stopPropagation()
+        event.preventDefault()
+        @hideEditMemberRolesView()

     @editContainer     = new KDView
       cssClass         : 'edit-container hidden'


fix log line

fix log line

--- services/document-updater/app/coffee/HttpController.coffee
+++ services/document-updater/app/coffee/HttpController.coffee
@@ -56,7 +56,7 @@
 			else if error?
 				return next(error)
 			else
-				logger.log project_id: project_id, result: ("#{doc._id}:#{doc.rev}" for doc in result), "got docs via http"
+				logger.log project_id: project_id, result: ("#{doc._id}:#{doc.v}" for doc in result), "got docs via http"
 				res.send result

 	setDoc: (req, res, next = (error) ->) ->


Reorganize

Reorganize

--- src/trix/installer.coffee
+++ src/trix/installer.coffee
@@ -12,7 +12,7 @@
   createEditor: ->
     if @browserIsSupported()
       @createStyleSheet()
-      @createTextElement()
+      @config.textElement = @createTextElement()
       new Trix.EditorController @config

   browserIsSupported: ->
@@ -39,25 +39,26 @@

     element = document.createElement("div")
     element.innerHTML = textarea.value
-    element.setAttribute("data-placeholder", textarea.getAttribute("placeholder"))
+    element.classList.add(name) for name in @classNames()
+
     element.setAttribute("contenteditable", "true")
     element.setAttribute("autocorrect", "off")
     element.setAttribute("spellcheck", "false")

-    for name in @classNames()
-      element.classList.add(name)
-
-    disableObjectResizingOnFocus(element)
-    proxyInputEvents(element, textarea)
+    if placeholder = textarea.getAttribute("placeholder")
+      element.setAttribute("data-placeholder", placeholder)

     textareaStyle = window.getComputedStyle(textarea)
     element.style[style] = textareaStyle[style] for style in textareaStylesToCopy
     element.style["min-height"] = textareaStyle["height"]

+    disableObjectResizingOnFocus(element)
+    proxyInputEvents(element, textarea)
+
     textarea.style["display"] = "none"
     textarea.parentElement.insertBefore(element, textarea)

-    @config.textElement = element
+    element

   classNames: ->
     result = ["trix-editor"]


Update asyncErrorTrap documentation.

Update asyncErrorTrap documentation.

--- src/utils/commands.coffee
+++ src/utils/commands.coffee
@@ -29,10 +29,26 @@

 # Trap error returned by asynchronous function in the callback arguments
 # by generating a callback wrapper that will call the callback only if
-# no errors was received. The error arguments isn't passed to the callback.
+# no errors was received. The error argument is not passed to the callback.
 #
 #     fs.readFile "/path/to/file", asyncErrorTrap (content) ->
 #       # do something with content
+#
+# Optionally, a function can be passed as first argument to receive the error,
+# typically the final callback you'll trigger in your async process.
+#
+#     asyncFunction = (callback) ->
+#       path = "/path/to/file"
+#       fs.readFile path, asyncErrorTrap callback, (content) ->
+#         # do something with content
+#         callback? null, content
+#
+# If an error occurs, the callback is automatically triggered with the error
+# as the first argument, following in that regard the pattern of node's async
+# functions. You should take care to pass your results after a null argument,
+# allowing callback to use the following pattern:
+#
+#     asyncFunction asyncErrorTrap (results...) -> # etc...
 asyncErrorTrap = (errCallback, callback) -> (err, args...) ->
   [errCallback, callback] = [callback, errCallback] unless callback?
   if err?


bongo: pass redis client down into bongo

--- node_modules_koding/bongo/lib/bongo.coffee
+++ node_modules_koding/bongo/lib/bongo.coffee
@@ -82,7 +82,7 @@
   constructor: (options) ->

     { @mq, @mongo, @resourceName, @root, @metrics, @cache
-      @fetchClient, @verbose, models, @kite, @mqConfig } = options
+      @fetchClient, @verbose, models, @kite, @mqConfig, @redisClient } = options

     @setClient @mongo  if @mongo
     Model.setCache @cache  if @cache


get smarter about managing windows

get smarter about managing windows

--- src/main.coffee
+++ src/main.coffee
@@ -15,7 +15,19 @@
 primaryDisplay = null
 windows = null

+closeWindows = ->
+  return unless windows?.length > 0
+  _windows = windows
+  windows = null
+  _windows.forEach (w) ->
+    try
+      w.close()
+    catch ex
+      'nothing'
+
 createWindows = (displaySettings) ->
+  if windows?.length > 0
+    closeWindows()
   windows = []
   # Create the browser window.
   displays = electronScreen.getAllDisplays()
@@ -94,14 +106,7 @@
     # window.webContents.openDevTools();

     window.on 'closed', ->
-      return unless windows?
-      _windows = windows
-      windows = null
-      _windows.forEach (w) ->
-        try
-          w.close()
-        catch ex
-          'nothing'
+      closeWindows()
       # app.quit()
       Launcher.show primaryDisplay


Create divs for each section"

Create divs for each section"

--- src/main.coffee
+++ src/main.coffee
@@ -13,9 +13,14 @@

 world = god(rx, config)

+div = rx.rxt.tags.div
 main = ->
   $('body').append(
+    div {id: "controls"}
+    div {id: "grid"}
+    div {id: "inspector"}
     draw world.find_child('layout')
+    #render world.find_child('ui')
   )

 # Instantiate our main view


Spawn fix.

Spawn fix.

--- src/main.coffee
+++ src/main.coffee
@@ -78,7 +78,8 @@
                     window.hurt.play()
                 else if window.player.thrusting
                     window.collect.play()
-                    window.enemies.push(new Enemy(window.game, enemy.x + 1000, enemy.y, window.enemies[i].maxHealth + 10))
+                    x = if enemy.x > 1500 then enemy.x - 1000 else enemy.x + 1000
+                    window.enemies.push(new Enemy(window.game, x, enemy.y, window.enemies[i].maxHealth + 10))
                     enemy.destroy()
                     window.player.maxHealth += 1
                     window.player.health += 1


add new stores to main

add new stores to main

--- src/main.coffee
+++ src/main.coffee
@@ -1,11 +1,17 @@
-actionManager = require './action-manager'
-ActionCreator = require './action-creator'
-Store         = require './store'
-invariant     = require './invariant'
+actionManager    = require './action-manager'
+ActionCreator    = require './action-creator'
+Store            = require './store'
+EntityStore      = require './entity-store'
+ListStore        = require './list-store'
+IndexedListStore = require './indexed-list-store'
+invariant        = require './invariant'

 module.exports = {
 	actionManager
 	ActionCreator
 	Store
+	EntityStore
+	ListStore
+	IndexedListStore
 	invariant
 }


added glob command to commander

added glob command to commander

--- src/mist.coffee
+++ src/mist.coffee
@@ -29,6 +29,7 @@

 config
   .version packageJson.version
+  .command 'glob [globs...]', 'Tests globs for file selection'
   .parse process.argv.slice(0, 2).concat mistArgs

 try


Bit more

Bit more

--- src/msgc.coffee
+++ src/msgc.coffee
@@ -14,40 +14,50 @@
     super robot

   send: (envelope, strings...) ->
-    console.log("**** send ****")
-    console.log(envelope)
-    console.log(strings)
-    console.log("**** send ****")
+    @robot.logger.debug("send")
+
+    out = ""
+    out = ("#{str}\n" for str in strings)
+    textMessage = {
+      "type": "text",
+      "id": envelope.message.id,
+      "username": envelope.user.id,
+      "room": envelope.room,
+      "text": out.join('')
+    }
+    console.log("textMessage:")
+    console.log(textMessage)
+    console.log("End textMessage")
+    json = JSON.stringify(textMessage)
+
+    console.log("json:")
+    console.log(json)
+    console.log("end json")
+
     @wss.clients.forEach (client) =>
-      client.send(JSON.stringify(strings))
+      console.log("Sending #{json}")
+      client.send(json)

   emote: (envelope, strings...) ->
-    console.log("**** emote ****")
-    @robot.logger.info("**** emote ****")
-    @send envelope, "* #{str}" for str in strings
+    @robot.logger.debug("emote")
+    @send envelope, strings

   reply: (envelope, strings...) ->
-    console.log("**** reply ****")
-    console.log(envelope)
-    console.log(strings)
-    console.log("**** reply ****")
-    strings = strings.map (s) -> "#{envelope.user.name}: #{s}"
-    @send envelope, strings...
+    @robot.logger.debug("reply")
+    @send envelope, strings

-  _on_text_message: (user, text) =>
-    @robot.logger.debug("Received text message from #{user.name} => #{text}")
-    @receive new TextMessage(user, text)
+  _onTextMessage: (user, id, text) =>
+    @robot.logger.debug("Received text message id #{id} from #{user.name} => #{text}")
+    @receive new TextMessage(user, text, id)

-  on_message: (message) =>
+  onMessage: (message) =>
     @robot.logger.debug("Received message #{message}")
     message = JSON.parse message
     user = @robot.brain.userForId(message.username)
+    user.room = message.room
     switch message.type
-        when "text" then @_on_text_message(user, message.text)
+        when "text" then @_onTextMessage(user, message.id, message.text)
         else @robot.logger.error("Unexpected message type #{message.type}")
-
-  on_heartbeat: (e) =>
-    @robot.logger.debug("heartbeat")

   run: ->
     # Hack robot to inject proxy settings
@@ -57,8 +67,7 @@
     @wss = new WebSocketServer {port: 4773}
     @wss.on 'connection', (ws) =>
       @robot.logger.info("Websocket connection opened")
-      ws.on 'message', @on_message
-      ws.on 'heartbeat', @on_heartbeat
+      ws.on 'message', @onMessage

     @robot.logger.info("Connected to hubot")
     @emit "connected"


Use env var for listen port

Use env var for listen port

--- src/msgc.coffee
+++ src/msgc.coffee
@@ -7,6 +7,10 @@

 PROXY_HOST = process.env.PROXY_HOST
 PROXY_PORT = process.env.PROXY_PORT
+HUBOT_MSGC_LISTEN_PORT = process.env.HUBOT_MSGC_LISTEN_PORT
+unless HUBOT_MSGC_LISTEN_PORT?
+  console.log "Missing HUBOT_MSGC_LISTEN_PORT environment variable.  Please set and try again."
+  process.exit(1)

 class MicrosoftGroupChatAdapter extends Adapter

@@ -64,7 +68,7 @@
     if (PROXY_HOST)
       @robot.http = @http

-    @wss = new WebSocketServer {port: 4773}
+    @wss = new WebSocketServer {port: HUBOT_MSGC_LISTEN_PORT}
     @wss.on 'connection', (ws) =>
       @robot.logger.info("Websocket connection opened")
       ws.on 'message', @onMessage


bug fix

bug fix

--- src/pjax.coffee
+++ src/pjax.coffee
@@ -24,7 +24,7 @@
       url = simple.url $link.attr 'href'

       if url
-        @load url,
+        @load url,
           nocache: $link.is '[data-pjax-nocache]'
           norefresh: $link.is '[data-pjax-norefresh]'

@@ -56,6 +56,7 @@
         $page[0].offsetHeight # force relow
         setTimeout ->
           $target = $('#' + url.hash)
+          return unless $target.length > 0
           targetOffset = $target.offset()
           $(document).scrollTop(targetOffset.top - 30)
             .scrollLeft(targetOffset.left - 30)
@@ -236,7 +237,8 @@
       return false

     # set cache again for posible dom change in unload event
-    @setCache()
+    page.html = @el.html()
+    @setCache page

     if page
       state = $.extend {}, page,
@@ -256,7 +258,7 @@
   setCache: (page) ->
     unless page
       $page = @el.children().first()
-      page =
+      page =
         url: @url.toString('relative')
         name: $page.data('page-name') || @pageTitle()
         html: @el.html()


apply new source ref scheme

apply new source ref scheme

--- src/mkts-table.coffee
+++ src/mkts-table.coffee
@@ -335,26 +335,26 @@
   @_ensure_unitvector me
   yield tex "\n\n"
   yield tex "\\par% Beginning of MKTS Table ==============================================================================\n"
-  yield texr '@1', "{\\setlength{\\fboxsep}{0mm}"
+  yield texr 'µ1', "{\\setlength{\\fboxsep}{0mm}"
   ### TAINT insert proper dimensions ###
-  yield texr '@1', "\\begin{minipage}[t][45mm][t]{100mm}"
-  yield texr '@1', "\\newdimen\\mktsTableUnitwidth\\setlength{\\mktsTableUnitwidth}{#{me.unitwidth}}"
-  yield texr '@1', "\\newdimen\\mktsTableUnitheight\\setlength{\\mktsTableUnitheight}{#{me.unitheight}}"
-  yield texr '@1', "\\begin{tikzpicture}[ overlay, yshift = 0mm, yscale = -1, line cap = rect ]"
-  yield texr '@1', "\\tikzset{x=#{me.unitwidth}};\\tikzset{y=#{me.unitheight}};"
+  yield texr 'µ2', "\\begin{minipage}[t][45mm][t]{100mm}"
+  yield texr 'µ3', "\\newdimen\\mktsTableUnitwidth\\setlength{\\mktsTableUnitwidth}{#{me.unitwidth}}"
+  yield texr 'µ4', "\\newdimen\\mktsTableUnitheight\\setlength{\\mktsTableUnitheight}{#{me.unitheight}}"
+  yield texr 'µ5', "\\begin{tikzpicture}[ overlay, yshift = 0mm, yscale = -1, line cap = rect ]"
+  yield texr 'µ6', "\\tikzset{x=#{me.unitwidth}};\\tikzset{y=#{me.unitheight}};"
   yield return

 #-----------------------------------------------------------------------------------------------------------
 @_walk_closing_events = ( me ) ->
-  yield texr '@1', "\\end{tikzpicture}"
-  yield texr '@1', "\\end{minipage}}"
+  yield texr 'µ7', "\\end{tikzpicture}"
+  yield texr 'µ8', "\\end{minipage}}"
   yield tex "\\par% End of MKTS Table ====================================================================================\n\n"
   yield return

 #-----------------------------------------------------------------------------------------------------------
 @_walk_style_events = ( me ) ->
   for key, value of me.styles
-    yield texr '@1', "\\tikzset{#{key}/.style={#{value}}}"
+    yield texr 'µ9', "\\tikzset{#{key}/.style={#{value}}}"
   yield return

 #-----------------------------------------------------------------------------------------------------------
@@ -363,13 +363,13 @@
   for designation, d of me.border_dimensions
     continue unless ( fieldborders = me.fieldborders[ designation ] )?
     if ( borderstyle = fieldborders[ 'left' ] )?
-      yield texr '@1', "\\draw[#{borderstyle}] (#{d.left},#{d.top}) -- (#{d.left},#{d.bottom});"
+      yield texr 'µ10', "\\draw[#{borderstyle}] (#{d.left},#{d.top}) -- (#{d.left},#{d.bottom});"
     if ( borderstyle = fieldborders[ 'right' ] )?
-      yield texr '@2', "\\draw[#{borderstyle}] (#{d.right},#{d.top}) -- (#{d.right},#{d.bottom});"
+      yield texr 'µ11', "\\draw[#{borderstyle}] (#{d.right},#{d.top}) -- (#{d.right},#{d.bottom});"
     if ( borderstyle = fieldborders[ 'top' ] )?
-      yield texr '@4', "\\draw[#{borderstyle}] (#{d.left},#{d.top}) -- (#{d.right},#{d.top});"
+      yield texr 'µ12', "\\draw[#{borderstyle}] (#{d.left},#{d.top}) -- (#{d.right},#{d.top});"
     if ( borderstyle = fieldborders[ 'bottom' ] )?
-      yield texr '@5', "\\draw[#{borderstyle}] (#{d.left},#{d.bottom}) -- (#{d.right},#{d.bottom});"
+      yield texr 'µ13', "\\draw[#{borderstyle}] (#{d.left},#{d.bottom}) -- (#{d.right},#{d.bottom});"
   #.........................................................................................................
   yield return

@@ -401,11 +401,11 @@
   for [ field_designation, content, ] from @_walk_most_recent_field_designations me, fieldhints_and_content_events
     d           = me.pod_dimensions[ field_designation ]
     valign_tex  = @_get_valign_tex me, me.valigns[ field_designation ] ? me.valigns[ '*' ] ? 'center'
-    yield texr '@1', "\\node[anchor=north west,inner sep=0mm] at (#{d.left},#{d.top})"
-    yield texr '@1', "{\\begin{minipage}[t][#{d.height}\\mktsTableUnitheight][#{valign_tex}]{#{d.width}\\mktsTableUnitwidth}"
+    yield texr 'µ14', "\\node[anchor=north west,inner sep=0mm] at (#{d.left},#{d.top})"
+    yield texr 'µ15', "{\\begin{minipage}[t][#{d.height}\\mktsTableUnitheight][#{valign_tex}]{#{d.width}\\mktsTableUnitwidth}"
     yield [ '.', 'noindent', null, {}, ]
     yield sub_event for sub_event in content
-    yield texr '@1', "\\end{minipage}};"
+    yield texr 'µ16', "\\end{minipage}};"
   #.........................................................................................................
   yield return

@@ -427,16 +427,16 @@
   bottom    = ( @_bottom_from_row_nr  me, me.grid.height ) + 3
   for col_nr in [ 1 .. me.grid.width + 1 ]
     x = @_left_from_col_nr me, col_nr
-    yield texr '@1', "\\draw[sDebugCellgrid] (#{x},#{top}) -- (#{x},#{bottom});"
+    yield texr 'µ18', "\\draw[sDebugCellgrid] (#{x},#{top}) -- (#{x},#{bottom});"
   #.........................................................................................................
   ### TAINT use fixed size like 1mm ###
   left      = ( @_left_from_col_nr    me, 1             ) - 3
   right     = ( @_right_from_col_nr   me, me.grid.width  ) + 3
   for row_nr in [ 1 .. me.grid.height + 1 ]
     y = @_top_from_row_nr me, row_nr
-    yield texr '@1', "\\draw[sDebugCellgrid] (#{left},#{y}) -- (#{right},#{y});"
-  #.........................................................................................................
-  yield texr '@1', "\\end{scope}"
+    yield texr 'µ19', "\\draw[sDebugCellgrid] (#{left},#{y}) -- (#{right},#{y});"
+  #.........................................................................................................
+  yield texr 'µ20', "\\end{scope}"
   #.........................................................................................................
   yield return

@@ -445,7 +445,7 @@
   unless @_should_debug me
     yield return
   #.........................................................................................................
-  yield texr '@1', "\\begin{scope}[on background layer]"
+  yield texr 'µ21', "\\begin{scope}[on background layer]"
   #.........................................................................................................
   for designation, d of me.field_dimensions
     ### TAINT use fixed size like 1mm ###
@@ -462,7 +462,7 @@
                  + " -- (#{right},#{bottom});", ]
     yield [ 'tex', "% MKTSTBL@26\n", ]
   #.........................................................................................................
-  yield texr '@1', "\\end{scope}"
+  yield texr 'µ22', "\\end{scope}"
   #.........................................................................................................
   yield return

@@ -470,19 +470,20 @@
 @_walk_debug_joints_events = ( me ) ->
   unless @_should_debug me
     yield return
+  @_ensure_grid me
   @_ensure_joint_coordinates  me
   #.........................................................................................................
-  yield texr '@1', "\\begin{scope}[on background layer]"
+  yield texr 'µ23', "\\begin{scope}[on background layer]"
   #.........................................................................................................
   ### TAINT use fixed size like 1mm ###
-  for [ col_letter, col_nr, ] from @_walk_column_letters_and_numbers me, 'short'
-    for row_nr from @_walk_row_numbers me, 'short'
-      x = ( @_left_from_col_nr  me, col_nr ) + 2
-      y = ( @_top_from_row_nr   me, row_nr ) + 2
-      cellkey = "#{col_letter}#{row_nr}"
+  for [ colletters, colnr, ] from IG.GRID.walk_colletters_and_colnrs me.grid
+    for rownr from IG.GRID.walk_rownrs me.grid
+      x = ( @_left_from_colnr  me, colnr ) + 2
+      y = ( @_top_from_rownr   me, rownr ) + 2
+      cellkey = "#{colletters}#{rownr}"
       yield tex "\\node[sDebugJoints] at (#{x},#{y}) {{\\mktsStyleCode{}#{cellkey}}}; "
   #.........................................................................................................
-  yield texr '@1', "\\end{scope}"
+  yield texr 'µ24', "\\end{scope}"
   #.........................................................................................................
   yield return


Fix unintentional search-replaces

Fix unintentional search-replaces

--- spec/Component.coffee
+++ spec/Component.coffee
@@ -205,9 +205,9 @@
       chai.expect(c.started).to.equal(false)
       chai.expect(c.isStarted()).to.equal(false)

-  describe 'with object-based noflo.IPs', ->
-
-    it 'should speak noflo.IP objects', (done) ->
+  describe 'with object-based IPs', ->
+
+    it 'should speak IP objects', (done) ->
       c = new noflo.Component
         inPorts:
           in:
@@ -320,7 +320,7 @@
       sout2 = new noflo.internalSocket.InternalSocket
       done()

-    it 'should trigger on noflo.IPs', (done) ->
+    it 'should trigger on IPs', (done) ->
       hadIPs = []
       c = new noflo.Component
         inPorts:
@@ -481,7 +481,7 @@
       sin2.post new noflo.IP 'data', 'bar',
         groups: ['bar']

-    it 'should receive noflo.IPs and be able to selectively find them', (done) ->
+    it 'should receive IPs and be able to selectively find them', (done) ->
       called = 0
       c = new noflo.Component
         inPorts:
@@ -1206,7 +1206,7 @@
           count: n++
           length: source.length

-    it 'should be safe dropping noflo.IPs', (done) ->
+    it 'should be safe dropping IPs', (done) ->
       c = new noflo.Component
         inPorts:
           in:


Fix forgotten renaming in spec

Fix forgotten renaming in spec

--- spec/lint-spec.coffee
+++ spec/lint-spec.coffee
@@ -1,9 +1,9 @@
-Lint = require '../lib/lint'
+Lint = require '../lib/atom-lint'

 # Use the command `window:run-package-specs` (cmd-alt-ctrl-p) to run specs.
 #
 # To run a specific `it` or `describe` block add an `f` to the front (e.g. `fit`
 # or `fdescribe`). Remove the `f` to unfocus the block.

-describe 'Lint', ->
+describe 'atom-lint', ->
   it 'MUST have specs :)'


:zap: only keep 16 tiles in memory

:zap: only keep 16 tiles in memory

--- src/TileSource.coffee
+++ src/TileSource.coffee
@@ -25,6 +25,9 @@

 module.exports = class TileSource
   cache: {}
+  cacheSize: 16
+  cached: []
+
   modes:
     MBTiles: 1
     VectorTile: 2
@@ -67,6 +70,10 @@
     if cached = @cache[[z,x,y].join("-")]
       return Promise.resolve cached

+    if @cached.length > @cacheSize
+      for tile in @cached.splice 0, Math.abs(@cacheSize-@cached.length)
+        delete @cache[tile]
+
     switch @mode
       when @modes.MBTiles then @_getMBTile z, x, y
       when @modes.HTTP then @_getHTTP z, x, y
@@ -93,7 +100,10 @@
         resolve @_createTile z, x, y, buffer

   _createTile: (z, x, y, buffer) ->
-    tile = @cache[[z,x,y].join("-")] = new Tile @styler
+    name = [z,x,y].join("-")
+    @cached.push name
+
+    tile = @cache[name] = new Tile @styler
     tile.load buffer

   _initPersistence: ->


Old API for Node.js' path

Old API for Node.js' path

--- webpack/cmdize.coffee
+++ webpack/cmdize.coffee
@@ -22,8 +22,7 @@
       x = path.parse dst = z.existsAt
       continue if '.js' != x.ext
       fs.unlink dst, ->
-      x.ext = '.bat'
-      delete x.base
+      x.base = x.name + '.bat'

       for q in dup [x.name, ':*'], debug
         if bat = yml[q]
@@ -50,10 +49,8 @@

 readYML = ->
   z = path.parse __filename
-  delete z.basename
-  z.ext = '.yml'
+  z.base = z.name + '.yml'
   z.dir = path.join z.dir, '../src'
-  delete z.base
   yaml.safeLoad fs.readFileSync path.format z

 dup = (array, debug)->


Small improvement.

Small improvement.

--- app/assets/javascripts/app/models/_application_model.js.coffee
+++ app/assets/javascripts/app/models/_application_model.js.coffee
@@ -150,16 +150,36 @@

   attributes = App.Model.attributesGet(optionalScreen, optionalAttributesList)

+  returns
+    {
+      'name': {
+        name:    'name'
+        display: 'Name'
+        tag:     'input'
+        type:    'text'
+        limit:   100
+        null:    false
+      },
+      'assignment_timeout': {
+        name:    'assignment_timeout'
+        display: 'Assignment Timeout'
+        tag:     'input'
+        type:    'text'
+        limit:   100
+        null:    false
+      },
+    }
+
   ###

   @attributesGet: (screen = undefined, attributes = false) ->
     if !attributes
-      attributes = clone( App[ @.className ].configure_attributes )
+      attributes = clone( App[ @.className ].configure_attributes, true )
     else
-      attributes = clone( attributes )
+      attributes = clone( attributes, true )

     # in case if no configure_attributes exist
-    return if !attributes
+    return {} if !attributes
     attributesNew = {}

     # check params of screen if screen is requested
@@ -192,9 +212,11 @@
     return true if @id[0] isnt 'c'
     return false

+  # App.Model.fullLocal(id)
   @fullLocal: (id) ->
     @_fillUp( App[ @className ].find( id ) )

+  # App.Model.full(id, callback, force, bind)
   @full: (id, callback = false, force = false, bind = false) ->
     url = "#{@url}/#{id}?full=true"

@@ -208,7 +230,7 @@
       data = App[ @className ].find( id )
       data = @_fillUp( data )
       if callback
-        callback( data )
+        callback( data, 'full' )
       return subscribeId

     # store callback and requested id
@@ -303,7 +325,7 @@
     console.log("Collection has changed", changedItems, localOrServer)

   params =
-    initFetch: true # fetch inital collection
+    initFetch: true # fetch initial collection

   @subscribeId = App.Model.subscribe( methodWhichIsCalledAtLocalOrServerSiteChange )

@@ -322,7 +344,7 @@
       )

       # fetch() all on network notify
-      events = "#{@className}:create #{@className}:update #{@className}:destroy"
+      events = "#{@className}:create #{@className}:update #{@className}:touch #{@className}:destroy"
       App.Event.bind(
         events
         =>
@@ -403,7 +425,7 @@
       )

       # subscribe and render data after server change
-      events = "#{@className}:create #{@className}:update #{@className}:destroy"
+      events = "#{@className}:create #{@className}:update #{@className}:touch #{@className}:destroy"
       App.Event.bind(
         events
         (item) =>


select-field shows now icon in the dropdown, when a previously selected mentor is removed from the selection

--- app/assets/javascripts/components/kid_mentor_schedules.js.cjsx
+++ app/assets/javascripts/components/kid_mentor_schedules.js.cjsx
@@ -8,7 +8,8 @@
 MAX_MENTORS_TO_DISPLAY = 10
 @KidMentorSchedules = React.createClass
   getInitialState: ->
-    mentorsToDisplay: _.keys(@props.mentors)
+    mentorsToDisplay: getMentorIds @props.mentors
+    visitedMentors: []
     filters:
       ect: null
       sex: null
@@ -42,11 +43,13 @@
     return filteredMentors
   getSelectedMentors: (filteredMentors) ->
     limit _.pick filteredMentors, @state.mentorsToDisplay
-  onChangeSelectedMentorsToDisplay: (mentorIds) ->
+  onChangeSelectedMentorsToDisplay: (mentorIds, removedMentors) ->
+    @setState visitedMentors: _.union @state.visitedMentors, removedMentors
     @setState mentorsToDisplay: mentorIds
   onChangeFilter: (key, value) ->
     filters = @state.filters
     filters[key] = value
+    @setState visitedMentors: [] # reset visits
     @setState filters: filters
     # select all mentors, if filter changes
     @selectAll()
@@ -128,8 +131,7 @@
   render: ->
     filteredMentors = @getFilteredMentors()
     selectedMentors = @getSelectedMentors filteredMentors
-
-
+    selectedMentorIds = getMentorIds selectedMentors

@@ -148,8 +150,9 @@

             <MentorsForDisplayingFilter
               mentors=filteredMentors
-              selection=_.keys(selectedMentors)
+              selection=selectedMentorIds
               onChange=@onChangeSelectedMentorsToDisplay
+              visitedMentors=@state.visitedMentors
             />

@@ -167,19 +170,31 @@
   onChange: (valuesAsString) ->
     if valuesAsString? and valuesAsString.length > 0
       values = valuesAsString.split(@DELEMITER).map (id) -> parseInt id, 10
-      @props.onChange limitAndRemoveFromBeginning values
+      selectedMentorIds = limitAndRemoveFromBeginning values
     else
-      @props.onChange []
+      selectedMentorIds = []
+    @triggerChange selectedMentorIds
+
+  triggerChange: (selectedMentorIds) ->
+    console.log @props.selection, selectedMentorIds
+    console.log _.difference @props.selection, selectedMentorIds
+    removedIds = _.difference @props.selection, selectedMentorIds
+    @props.onChange selectedMentorIds, removedIds
+
   selectAll: ->
-    @props.onChange _.keys @props.mentors
+    @triggerChange _.keys @props.mentors
+
   render: ->
     options = for id,mentor of @props.mentors
+      visited: parseInt(id,10) in @props.visitedMentors
       label: displayName mentor
       value: mentor.id.toString()
       style:
         color: mentor.colors.text
         backgroundColor: mentor.colors.background
         borderColor: mentor.colors.text
+    options = _.sortBy options, "visited"
+
     selectedIds = []
     for id in @props.selection # filter out not available values
       selectedIds.push id if @props.mentors[id]?
@@ -193,12 +208,15 @@

       else
         ({_.size @props.mentors})
+    optionRenderer = (option) ->
+      {if option.visited then } {option.label}

         <Select
           options=options
           multi=true
+          optionRenderer=optionRenderer
           delimiter=@DELEMITER
           value=value
           onChange=@onChange
@@ -221,6 +239,7 @@
       when "true" then true
       when "false" then false
       else null
+
     @props.onChange? "ects", asBoolean event.target.value
   onChangeSchool: (event) ->
     sanitize = (value) -> if _.size(value) == 0 then null else parseInt value, 10
@@ -411,6 +430,9 @@
 hasPrimaryMentor = (kid) -> kid.mentor_id?
 hasSecondaryMentor = (kid) -> kid.secondary_mentor_id?

+getMentorIds = (mentorsObject) ->
+  (parseInt id, 10 for id in _.keys(mentorsObject))
+
 createTimeCellClasses = ({primaryClass, day, lastTime, time, nextTime, schedules}) ->
   classNames primaryClass,
     'time-available': availableInSchedule schedules, day, time


When lazy-loading an image via https fails, a second attempt is made with http and vice versa.

When the scheme is missing, https is attempted first.

--- app/assets/javascripts/ng-services/ng-LazyLoadingSvc.js.coffee
+++ app/assets/javascripts/ng-services/ng-LazyLoadingSvc.js.coffee
@@ -15,27 +15,53 @@
     # Create a clone of the image, hide it and load the actual image there. If it loads successfully,
     # hide the placeholder image and show the loaded image, otherwise hide both images.
     data_src = img.attr 'data-src'
-    uri = new URI(data_src).normalize()
-
-    # Convert relative URIs to absolute, using the feed hostname
-    if uri.is('relative') && entry?
-      feed = findSvc.find_feed entry.feed_id
-      feed_uri = new URI(feed.url).normalize()
-      uri.host feed_uri.host()

     if !data_src || data_src?.trim()?.length == 0
       # If data-src is blank, just hide the spinner
       img.addClass 'hidden'
     else
       # If data-src is not blank, lazy-load the image
+      uri = new URI(data_src).normalize()
+
+      # Convert relative URIs to absolute, using the feed hostname
+      if uri.is('relative') && entry?
+        feed = findSvc.find_feed entry.feed_id
+        feed_uri = new URI(feed.url).normalize()
+        uri.host feed_uri.host()
+
+      # If no scheme has been specified, try with https://
+      scheme = uri.scheme()
+      if !scheme
+        uri.scheme 'https'
+
       img.removeAttr('data-src')
       loaded_img = img.clone().addClass('loading').removeAttr('src').insertAfter(img)
       loaded_img.on 'error', ->
-        img.addClass 'hidden'
+        # If https failed try with http, and the other way around
+        if uri.scheme() == 'https'
+          uri.scheme 'http'
+        else
+          uri.scheme 'https'
+
+        loaded_img.off 'error load'
+        loaded_img.on 'error', ->
+          img.addClass 'hidden'
+        .on 'load', ->
+          show_loaded_image img, loaded_img
+        loaded_img.attr 'src', uri.toString()
       .on 'load', ->
-        img.addClass 'hidden'
-        animationsSvc.show_image loaded_img
+        show_loaded_image img, loaded_img
       loaded_img.attr 'src', uri.toString()
+
+  #--------------------------------------------
+  # PRIVATE FUNCTION: Show a successfully loaded image instead of the spinning placeholder.
+  # Receives as arguments:
+  # - jQuery object wrapping the placeholder img
+  # - jQuery object wrapping the loaded img
+  #--------------------------------------------
+  show_loaded_image = (placeholder_img, loaded_img) ->
+    placeholder_img.addClass 'hidden'
+    animationsSvc.show_image loaded_img

   service =


_modified does not need to be a DS.attr

_modified does not need to be a DS.attr

--- app/assets/javascripts/trade/models/sandbox_shipment.js.coffee
+++ app/assets/javascripts/trade/models/sandbox_shipment.js.coffee
@@ -13,4 +13,4 @@
   sourceCode: DS.attr('string')
   year: DS.attr('string')
   _destroyed: DS.attr('boolean')
-  _modified: DS.attr('boolean')
+  _modified: false


Naming of decoder variable

Naming of decoder variable

--- src/stream.iced
+++ src/stream.iced
@@ -17,10 +17,10 @@

 exports.StreamDecoder = class StreamDecoder extends stream.Transform

-  constructor : (@encoder) ->
-    super({highWaterMark : calculate_block_size(encoder.out_block_len)})
+  constructor : (@decoder) ->
+    super({highWaterMark : calculate_block_size(decoder.out_block_len)})

   _transform : (chunk, encoding, cb) ->
-    @push(@encoder.decode(chunk))
+    @push(@decoder.decode(chunk))
     @read(0)
     cb()


Social: add a new function for fetching channels of a group

--- workers/social/lib/social/models/socialapi/requests.coffee
+++ workers/social/lib/social/models/socialapi/requests.coffee
@@ -25,7 +25,19 @@

   request
     url    : "#{SOCIAL_API_URL}/channel/#{data.channelId}/history"
-    qs     : {accountId: data.accountId}
+    qs     : data
+    json   : true
+    body   : data
+    method : 'GET'
+  , wrapCallback callback
+
+fetchGroupChannels = (data, callback)->
+  if not data.groupName or not data.accountId
+    return callback { message: "Request is not valid for creating channel"}
+
+  request
+    url    : "#{SOCIAL_API_URL}/channel"
+    qs     : data
     json   : true
     body   : data
     method : 'GET'


add attachment support on top of v0.1.4 (#148)

--- src/rocketchat.coffee
+++ src/rocketchat.coffee
@@ -31,6 +31,10 @@
 	sendPrivate: (strings...) ->
 		@robot.adapter.sendDirect @envelope, strings...

+class AttachmentMessage extends TextMessage
+	constructor: (@user, @attachment, @text, @id) ->
+		super @user, @text, @id
+
 class RocketChatBotAdapter extends Adapter

 	run: =>
@@ -137,13 +141,31 @@
 								edited = new Date(newmsg.editedAt.$date)
 								curts = if edited > curts then edited else curts
 							@robot.logger.info "Message receive callback id " + newmsg._id + " ts " + curts
-							@robot.logger.info "[Incoming] #{newmsg.u.username}: #{newmsg.msg}"
+							@robot.logger.info "[Incoming] #{newmsg.u.username}: #{if newmsg.file? then newmsg.attachments[0].title else newmsg.msg}"

 							if curts > @lastts
 								@lastts = curts
 								if newmsg.t isnt 'uj'
 									user = @robot.brain.userForId newmsg.u._id, name: newmsg.u.username, room: newmsg.rid
-									message = new TextMessage(user, newmsg.msg, newmsg._id)
+
+									# check for the presence of attachments in the message
+									if newmsg.file? and newmsg.attachments.length
+										attachment = newmsg.attachments[0]
+
+										if attachment.image_url?
+											attachment.link = "#{RocketChatURL}#{attachment.image_url}"
+											attachment.type = 'image'
+										else if attachment.audio_url?
+											attachment.link = "#{RocketChatURL}#{attachment.audio_url}"
+											attachment.type = 'audio'
+										else if attachment.video_url?
+											attachment.link = "#{RocketChatURL}#{attachment.video_url}"
+											attachment.type = 'video'
+
+										message = new AttachmentMessage user, attachment, attachment.title, newmsg._id
+									else
+										message = new TextMessage user, newmsg.msg, newmsg._id
+
 									isDM = newmsg.rid.indexOf(userid) > -1
 									startOfText = if message.text.indexOf('@') == 0 then 1 else 0
 									robotIsNamed = message.text.indexOf(@robot.name) == startOfText || message.text.indexOf(@robot.alias) == startOfText


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/text.coffee
+++ src/text.coffee
@@ -7,7 +7,7 @@

   self = GameObject(I)

-  self.bind "update", ->
+  self.bind "afterUpdate", ->
     I.y -= 1
     I.alpha = 1 - (I.age / I.duration)


Uses Backbone from the app

Uses Backbone from the app

--- src/client/api.coffee
+++ src/client/api.coffee
@@ -35,7 +35,7 @@
         sandbox = app.sandbox

         _         = require('underscore')
-        Backbone  = require('backbone')
+        Backbone  = app.core.mvc
         easyXDM   = require('easyXDM')

         slice = Array.prototype.slice


Fix for showing the gif instead of the placeholder jpg

Related to #2 from @virtualswede

--- src/codinglove.coffee
+++ src/codinglove.coffee
@@ -57,7 +57,7 @@

 get_meme_image = (body, selector)->
   $ = cheerio.load(body)
-  $(selector).first().attr('src')
+  $(selector).first().attr('src').replace(/\.jpe?g/i, '.gif')

 get_meme_txt = (body, selector)->
   $ = cheerio.load(body)


allow format obj to be passed in

allow format obj to be passed in

--- src/line.coffee
+++ src/line.coffee
@@ -77,14 +77,21 @@
     return [@leaves.last, offset - @leaves.last.length]   # Should never occur unless length calculation is off

   format: (name, value) ->
-    format = @doc.formats[name]
-    # TODO reassigning @node might be dangerous...
-    if format.isType(Format.types.LINE)
-      @node = format.add(@node, value)
-    if value
-      @formats[name] = value
+    if _.isObject(name)
+      formats = name
     else
-      delete @formats[name]
+      formats = {}
+      formats[name] = value
+    _.each(formats, (value, name) =>
+      format = @doc.formats[name]
+      # TODO reassigning @node might be dangerous...
+      if format.isType(Format.types.LINE)
+        @node = format.add(@node, value)
+      if value
+        @formats[name] = value
+      else
+        delete @formats[name]
+    )
     this.resetContent()

   formatText: (offset, length, name, value) ->


implement `get_metrics()`

implement `get_metrics()`


main: fix --stdout kludge

main: fix --stdout kludge

thx @dodo

--- src/main.coffee
+++ src/main.coffee
@@ -26,7 +26,8 @@
     config.merge(require(opts.config))

     # Kludge:
-    config.logging.stdout ?= opts.stdout
+    if opts.stdout
+        config.logging.stdout = true
     # Logger
     logger_ = require('./logger')
     logger_.setConfig config.logging


Since files passed in command line won't trigger 'open-file' any more, there is no need to clean duplicate paths.

It also seems that there is a typo...

--- src/main.coffee
+++ src/main.coffee
@@ -25,7 +25,6 @@

     args.pathsToOpen = args.pathsToOpen.map (pathToOpen) ->
       path.resolve(args.executedFrom ? process.cwd(), pathToOpen)
-    args.pathToOpen = _.uniq(args.pathsToOpen)

     AtomApplication.open(args)


Opening a folder works!

Opening a folder works!

--- src/main.coffee
+++ src/main.coffee
@@ -110,25 +110,29 @@

     ipc.on 'open-folder', =>
       currentWindow = BrowserWindow.getFocusedWindow()
-      dialog.openFolder currentWindow, {}, (result, paths...) => @createAtomWindow()
+      dialog.openFolder currentWindow, {}, (result, paths...) =>
+        console.log paths
+        @createAtomWindow(path) for path in paths

-  createAtomWindow: ->
+  createAtomWindow: (path) ->
     new AtomWindow
+      path: path
       bootstrapScript: 'window-bootstrap',
       resourcePath: @resourcePath

 class AtomWindow
   browserWindow: null
-  bootstrapScript: null
-  resourcePath: null
+  path: null

-  constructor: ({@bootstrapScript, @resourcePath}) ->
+  constructor: ({bootstrapScript, resourcePath, @path}) ->
     @browserWindow = new BrowserWindow width: 800, height: 600, show: false, title: 'Atom'
     @handleEvents()

     atomApplication.windows.push @browserWindow

-    url = "file://#{@resourcePath}/static/index.html?bootstrapScript=#{@bootstrapScript}&resourcePath=#{@resourcePath}"
+    url = "file://#{resourcePath}/static/index.html?bootstrapScript=#{bootstrapScript}&resourcePath=#{resourcePath}"
+    url += "&pathToOpen=#{@path}" if @path
+
     @browserWindow.loadUrl url
     @browserWindow.show()


:lipstick:

:lipstick:

--- src/main.coffee
+++ src/main.coffee
@@ -150,7 +150,7 @@

   handleEvents: ->
     @browserWindow.on 'destroyed', =>
-      atomApplication.windows.splice atomApplication.windows.indexOf(win), 1
+      atomApplication.windows.splice atomApplication.windows.indexOf(this), 1

     @browserWindow.on 'close', (event) =>
       event.preventDefault()


Fix comments.

Fix comments.

--- src/mock.coffee
+++ src/mock.coffee
@@ -229,11 +229,11 @@
 # mock() is a convenience function to ensure that _build_errors() is
 # called on mock objects.  As such, it takes a function (the test code)
 # argument, creates five mock objects, invokes the function with the
-# five mocks, and then calls _build_errors() on those mocks, throw an
+# five mocks, and then calls _build_errors() on those mocks, throwing an
 # error if any errors are found.
 #
 # _build_errors() is a "private" method on Mocks; check() should really
-# be used but it results in a try/catch block an messier code.  Could
+# be used but it results in a try/catch block and messier code.  Could
 # make _build_errors() public.
 #
 mock = (fn) ->


Wordsmith some comments.

Wordsmith some comments.

--- src/mock.coffee
+++ src/mock.coffee
@@ -196,10 +196,10 @@

 #
-# mock() is a shorthand function to ensure that .check() is called
+# mock() is a convenience function to ensure that .check() is called
 # on mock objects.  As such, it takes a function (the test code)
 # argument, creates five mock objects, invokes the function with
-# the five mocks, and then calls check() on the mocks.
+# the five mocks, and then calls check() on those mocks.
 #
 mock = (fn) ->
   mocks = ( new Mock() for i in [1..5] )


fix typo

fix typo

--- src/wrap.coffee
+++ src/wrap.coffee
@@ -113,7 +113,7 @@
             @extensions.push(ext)
             @filters.push (body, file) =>
                 if file.slice(-ext.length) is ext
-                    f.call(this, body, file)
+                    fn.call(this, body, file)
                 else body
         else
             @filters.push(ext)


fix: #2 Double unhandled rejection log

fix: #2 Double unhandled rejection log

--- src/yaku.coffee
+++ src/yaku.coffee
@@ -412,8 +412,9 @@
 		if typeof onFulfilled == $function
 			p2._onFulfilled = onFulfilled
 		if typeof onRejected == $function
-			p1[$hasUnhandledRejection] = false
 			p2._onRejected = onRejected
+
+		p1[$hasUnhandledRejection] = false

 		# 2.2.6
 		if p1._state == $pending


opt: format

opt: format

--- src/yaku.coffee
+++ src/yaku.coffee
@@ -210,23 +210,24 @@
 	@onUnhandledRejection: (reason, p) ->
 		return if not isObject console

-		$info = 'Unhandled Rejection:'
-
-		hStack = ''
+		hStack = '\n'
 		if isLongStackTrace and p[$handlerStack]
 			while p
 				hStack = p[$handlerStack] + hStack
 				p = p[$prePromise]

-		if typeof __filename == 'string'
-			hStack = hStack.replace ///.+#{__filename}.+\n///g, ''
+		format = (str) ->
+			if typeof __filename == 'string'
+				str.replace ///.+#{__filename}.+\n///g, ''
+			else
+				str

 		stack = if reason
-			reason.stack or reason
+			format(reason.stack) or reason
 		else
 			reason

-		console.error $info, stack, '\n' + hStack
+		console.error 'Unhandled Rejection:', stack, format(hStack)

 	isLongStackTrace = false


fix node joined

fix node joined

--- src/node.coffee
+++ src/node.coffee
@@ -46,7 +46,7 @@
     @set('handleIn', new Point(0,0).subtract(point)) if @isJoined

   computeIsjoined: ->
-    @isJoined = (not @handleIn and not @handleOut) or (@handleIn and @handleOut and @handleIn.x == -@handleOut.x and @handleIn.y == -@handleOut.y)
+    @isJoined = (not @handleIn and not @handleOut) or (@handleIn and @handleOut and Math.round(@handleIn.x) == Math.round(-@handleOut.x) and Math.round(@handleIn.y) == Math.round(-@handleOut.y))

   set: (attribute, value) ->
     old = @[attribute]


Deprecate theorist-provided behaviors in Pane

--- src/pane.coffee
+++ src/pane.coffee
@@ -207,6 +207,21 @@
         Grim.deprecate("Subscribing via ::on is deprecated. Use event subscription methods instead.")
     super

+  behavior: (behaviorName) ->
+    switch behaviorName
+      when 'active'
+        Grim.deprecate("The $active behavior property is deprecated. Use ::observeActive or ::onDidChangeActive instead.")
+      when 'container'
+        Grim.deprecate("The $container behavior property is deprecated.")
+      when 'activeItem'
+        Grim.deprecate("The $activeItem behavior property is deprecated. Use ::observeActiveItem or ::onDidChangeActiveItem instead.")
+      when 'focused'
+        Grim.deprecate("The $focused behavior property is deprecated.")
+      else
+        Grim.deprecate("Pane::behavior is deprecated. Use event subscription methods instead.")
+
+    super
+
   # Called by the view layer to indicate that the pane has gained focus.
   focus: ->
     @focused = true


Bind Pane view's focus to focus state on the model

It's a bit tricky… we don't want to blur the model when focusing the
pane's active view causes a focusout event on the pane, so we use
::suppressBlur on the model to prevent it from blurring as the focus
is transferred.

--- src/pane.coffee
+++ src/pane.coffee
@@ -36,13 +36,11 @@
     toProperty: 'model'

   previousActiveItem: null
-  focusOnAttach: false

   # Private:
   initialize: (args...) ->
     if args[0] instanceof PaneModel
       @model = args[0]
-      @focusOnAttach = @model.focused
     else
       @model = new PaneModel(items: args)
       @model._view = this
@@ -50,6 +48,9 @@
     @onItemAdded(item) for item in @items
     @viewsByItem = new WeakMap()
     @handleEvents()
+
+  hasFocus: ->
+    @is(':focus') or @is(':has(:focus)')

   handleEvents: ->
     @subscribe @model.$activeItem, 'value', @onActiveItemChanged
@@ -59,9 +60,21 @@
     @subscribe @model, 'before-item-destroyed', @onBeforeItemDestroyed
     @subscribe @model, 'item-destroyed', @onItemDestroyed

-    @subscribe this, 'focus', => @activeView?.focus(); false
-    @subscribe this, 'focusin', => @makeActive(); @model.focus()
-    @subscribe this, 'focusout', => @model.blur()
+    @subscribe @model.$focused, 'value', (focused) =>
+      if focused
+        @focus() unless @hasFocus()
+      else
+        @blur() if @hasFocus()
+
+    @subscribe this, 'focus', =>
+      @model.suppressBlur => @activeView?.focus()
+      false
+
+    @subscribe this, 'focusin', =>
+      @makeActive()
+      @model.focus()
+
+    @subscribe this, 'focusout', (e) => @model.blur()

     @command 'pane:save-items', => @saveItems()
     @command 'pane:show-next-item', => @showNextItem()
@@ -93,9 +106,7 @@

   # Private:
   afterAttach: (onDom) ->
-    if @focusOnAttach and onDom
-      @focusOnAttach = null
-      @focus()
+    @focus() if @model.focused and onDom

     return if @attached
     @attached = true
@@ -141,7 +152,8 @@
     @itemViews.children().not(view).hide()
     @itemViews.append(view) unless view.parent().is(@itemViews)
     view.show() if @attached
-    view.focus() if isFocused
+    if isFocused
+      @model.suppressBlur -> view.focus()
     @activeView = view
     @trigger 'pane:active-item-changed', [item]

@@ -223,12 +235,10 @@
   remove: (selector, keepData) ->
     return super if keepData

-    if @is(':has(:focus)')
-      @getContainer().focusNextPane() or atom.workspaceView?.focus()
-    else if @isActive()
+    @unsubscribe()
+    @model.destroy() unless @model.isDestroyed()
+
+    if @isActive()
       @getContainer().makeNextPaneActive()

-    @parent().view().removeChild(this) unless keepData
-    item.destroy?() for item in @getItems()
-
     super


tasks: full support for sorting tasks

tasks: full support for sorting tasks

--- salvus/page/tasks.coffee
+++ salvus/page/tasks.coffee
@@ -115,20 +115,34 @@
             position     : opts.position
             cb           : opts.cb

+    sort_task_list: () =>
+        # TODO: define f in terms of various sort crition based on UI
+        f = (task1, task2) =>
+            if task1.position < task2.position
+                return -1
+            else if  task1.position > task2.position
+                return 1
+            else
+                return 0
+        @task_list.tasks.sort(f)
+
     render_task_list: () =>
         search = []
         for x in @element.find(".salvus-tasks-search").val().toLowerCase().split()
             x = $.trim(x)
             if x.length > 0
                 search.push(x)
-        console.log(search.length)
         if search.length == 0
             @element.find(".salvus-tasks-search-describe").hide()
         else
             @element.find(".salvus-tasks-search-describe").show().find("span").text(search.join(' '))

         @elt_task_list.empty()
+        @sort_task_list()
+        first_task = undefined
         for task in @task_list.tasks
+            if not @showing_done and task.done
+                continue
             skip = false
             t = task.title.toLowerCase()
             for s in search
@@ -137,15 +151,58 @@
                     continue
             if not skip
                 @render_task(task)
-        if not @current_task and @task_list.tasks.length > 0
-            @set_current_task(@task_list.tasks[0])
-        @elt_task_list.sortable()
-
-    render_task: (task) =>
-        if not @showing_done and task.done
-            return # nothing to do
+                if not first_task?
+                    first_task = task
+
+        if not @current_task? and first_task?
+            @set_current_task(first_task)
+
+        @elt_task_list.sortable
+            containment : @element
+            update      : (event, ui) =>
+                e    = ui.item
+                task = e.data('task')
+                # determine the previous and next tasks and their position numbers.
+                prev = e.prev()
+                next = e.next()
+                # if no next or previous, this shouldn't get called (but definitely nothing to do)
+                if prev.length == 0 and next.length == 0
+                    return # nothing to do
+                # if no previous, make our position the next position -1
+                if prev.length == 0
+                    @save_task_position(task, next.data('task').position - 1)
+                # if no next, make our position the previous + 1
+                else if next.length == 0
+                    @save_task_position(task, prev.data('task').position + 1)
+                # if they are the same pos (due to very rare conflict during async add, which can happen),
+                # recompute and save all task positions
+                else if  prev.data('task').position == next.data('task').position
+                    i = 0
+                    @sort_task_list()
+                    for i in [0...@task_list.tasks.length]
+                        @save_task_position(@task_list.tasks[i], i)
+                    @save_task_position(task, (prev.data('task').position + next.data('task').position)/2)
+                # now they are different: set our position to the average of adjacent positions.
+                else
+                    @save_task_position(task, (prev.data('task').position + next.data('task').position)/2)
+
+    save_task_position: (task, position) =>
+        task.position = position
+        salvus_client.edit_task
+            task_list_id : @task_list_id
+            task_id      : task.task_id
+            project_id   : @project_id
+            position     : position
+            cb           : (err) =>
+                if err
+                    alert_message(type:"warning", message:"Problem saving new task position -- #{to_json(err)}")
+
+    render_task: (task, top) =>
         t = task_template.clone()
-        @elt_task_list.append(t)
+        if top
+            @elt_task_list.prepend(t)
+        else
+            @elt_task_list.append(t)
         task.element = t
         t.click () => @set_current_task(task)
         t.find(".salvus-task-title").click () => @edit_title(task)
@@ -156,6 +213,7 @@
             t.find(".salvus-task-viewer-not-done").hide()
         if @current_task? and task.task_id == @current_task.task_id
             @set_current_task(task)
+        t.data('task',task)
         @display_last_edited(task)
         @display_title(task)

@@ -241,7 +299,7 @@
                     @set_done(task, not done)
                 else
                     if done and not @showing_done
-                        task.element.fadeOut(3000, task.element.remove)
+                        task.element.fadeOut(1000, task.element.remove)

     init_create_task: () =>
         create_task_input = @element.find(".salvus-tasks-new")
@@ -249,10 +307,13 @@
             if misc_page.is_enter(evt)
                 title = create_task_input.val()
                 create_task_input.val('')
-                position = 0  # TODO
+                if @task_list.tasks.length == 0
+                    position = 0
+                else
+                    position = @task_list.tasks[0].position - 1
                 task = {title:title, position:position, last_edited:new Date() - 0}
-                @task_list.tasks.push(task)
-                t = @render_task(task)
+                @task_list.tasks.unshift(task)
+                t = @render_task(task, true)
                 t.icon_spin(start:true, delay:500)
                 @create_task
                     title : title


Logging debugging info for #204

Logging debugging info for #204

--- lib/git.coffee
+++ lib/git.coffee
@@ -175,7 +175,11 @@
     if directory?
       project.repositoryForDirectory(directory).then (repo) ->
         submodule = repo.repo.submoduleForPath(path)
-        if submodule? then resolve(submodule) else resolve(repo)
+        if submodule?
+          console.debug "Git-plus: submodule path is", submodule.getPath()
+          console.debug "Git-plus: submodule working directory is", submodule.getWorkingDirectory()
+          resolve(submodule)
+        else resolve(repo)
       .catch (e) ->
         reject(e)
     else


copy menu in console

copy menu in console

--- menus/ink.cson
+++ menus/ink.cson
@@ -1,8 +1,9 @@
 'context-menu':
   '.ink.inline': [
-    {
-      'label': 'Clear'
-      'command': 'inline-results:clear'
-    }
+    {label: 'Clear', command: 'inline-results:clear'}
     {type: 'separator'}
   ]
+  '.ink-console': [
+    {label: 'Copy', command: 'core:copy'}
+    {type: 'separator'}
+  ]


Fix lint issue with line length

Fix lint issue with line length


Work around broken coffeescript

Work around broken coffeescript

Fixes #3417

--- app/assets/volume/spreadsheet.coffee
+++ app/assets/volume/spreadsheet.coffee
@@ -935,11 +935,11 @@
                 add("Change all " + info.record.displayName + " " + info.metric.name + " to '" + i + "'",
                   (cell) -> saveMeasure(cell, info.record, info.metric, i),
                   i == input)
-              add("Create new " + info.category.name + " with " + info.metric.name + " '" + i + "'"
-                , (cell) -> setRecord(cell, info).then (r) ->
-                    saveMeasure(cell, r, info.metric, i) if r
-                    return
-                , i == input)
+              add("Create new " + info.category.name + " with " + info.metric.name + " '" + i + "'",
+                (cell) -> setRecord(cell, info).then((r) ->
+                  saveMeasure(cell, r, info.metric, i) if r
+                  return),
+                i == input)
           o

         optionCompletions = (input) ->


More ss editing fixes

More ss editing fixes

--- app/assets/volume/spreadsheet.coffee
+++ app/assets/volume/spreadsheet.coffee
@@ -339,7 +339,7 @@
                 populateSlotData(j, n, slot)
                 if !Editing && 'age' of rr
                   populateDatum(j, 'slot', n, 'age', rr.age)
-                deps[j] = 1
+                deps[j] = n
           ### jshint ignore:end ###

           i
@@ -413,7 +413,7 @@
           v = info.v
           switch info.metric.id
             when 'name'
-              if info.d
+              if info.d?
                 a = cell.appendChild(document.createElement('a'))
                 if info.c == 'asset'
                   icon = a.appendChild(document.createElement('img'))
@@ -460,6 +460,10 @@
             cell.classList.add('blank')
             v = info.metric.assumed || ''
           cell.appendChild(document.createTextNode(v))
+          cell.id = info.id
+          if info.d?
+            cell.classList.add(cls = info.p + info.d)
+            cell.classList.add(cls + '_' + info.metric.id)
           return

         # Add a td element to tr r with value c and id i
@@ -469,7 +473,6 @@
             info.cell.className = 'null'
           else
             generateText(info)
-            info.cell.id = info.id
           return

         generateMultiple = (info) -> # (col, cols, row, i, n, t) ->
@@ -513,9 +516,9 @@
           return unless l = ms.length
           t = info.count
           r = Data[info.c]
-          cls = info.p
           if td = generateMultiple(info) # (col, l, row, i, n, t)
             unless info.hasOwnProperty('n')
+              cls = info.p
               for n in [0..t-1] by 1
                 td.classList.add(cls + r.id[n][info.i])
             return
@@ -527,9 +530,6 @@
             info.d = r.id[info.n][info.i]
             info.id = pre + (info.cols.start+mi) + post
             generateCell(info)
-            if info.v != null
-              info.cell.classList.add(ri = cls + info.d)
-              info.cell.classList.add(ri + '_' + info.metric.id)
           return

         # Fill out rows[i].


Temp: ThangTypes don't require kind.

Temp: ThangTypes don't require kind.

--- app/schemas/models/thang_type.coffee
+++ app/schemas/models/thang_type.coffee
@@ -143,7 +143,7 @@
       description: 'Snap to this many meters in the y-direction.'
   components: c.array {title: 'Components', description: 'Thangs are configured by changing the Components attached to them.', uniqueItems: true, format: 'thang-components-array'}, ThangComponentSchema  # TODO: uniqueness should be based on 'original', not whole thing

-ThangTypeSchema.required = ['kind']
+ThangTypeSchema.required = []

 ThangTypeSchema.default =
   raw: {}


MO Dashboard: sort by profitability DESC, profits DESC

--- app/assets/javascripts/lib/models/merit_order.coffee
+++ app/assets/javascripts/lib/models/merit_order.coffee
@@ -34,18 +34,18 @@
   # Sort by two fields
   #
   sorting_function: (a,b) =>
-    pa = @profitability_index a
-    pb = @profitability_index b
-    ca = a.profits
-    cb = b.profits
+    profitability_a = @profitability_index a
+    profitability_b = @profitability_index b
+    profits_a = a.profits_per_mwh_electricity
+    profits_b = b.profits_per_mwh_electricity
     # sort by profitability (profitability < c.p < unprofitable)
-    if pa != pb
-      return -1 if pa < pb
-      return 1 if pa > pb
+    if profitability_a != profitability_b
+      return -1 if profitability_a < profitability_b
+      return  1 if profitability_a > profitability_b
     # sort by descending profits
-    return -1 if ca > cb
-    return 1 if ca < cb
-    return 0
+    return -1 if profits_a > profits_b
+    return  1 if profits_a < profits_b
+    return  0

   # Index used for sorting
   #