Allow migrations to request updating all references.

--- server.coffee
+++ server.coffee
@@ -401,6 +401,9 @@
     initializing = false

   @_Migration: class
+    updateAll: =>
+      @_updateAll = true
+
     forward: (db, collectionName, currentSchema, newSchema, callback) =>
       db.collection collectionName, (error, collection) =>
         return callback error if error
@@ -528,6 +531,8 @@
     db = MongoInternals.defaultRemoteCollectionDriver().mongo.db
     assert db

+    updateAll = false
+
     currentSchema = '1.0.0'
     currentSerial = 0
     currentName = initialName
@@ -550,9 +555,13 @@
       else
         newName = currentName

+      migration._updateAll = false
+
       future = new Future()
       migration.forward db, currentName, currentSchema, newSchema, future.resolver()
       migrated = future.wait()
+
+      updateAll = true if migrated and migration._updateAll

       if i < migrationsPending
         count = globals.Document.Migrations.update
@@ -621,8 +630,11 @@

     @Meta.schema = currentSchema

+    # Return if updateAll should be called
+    updateAll
+
   @setupMigrations: ->
-    @migrate()
+    updateAll = @migrate()

     @Meta.collection.find(
       _schema:
@@ -639,6 +651,9 @@
           $set:
             _schema: @Meta.schema

+    # Return if updateAll should be called
+    updateAll
+
   @updateAll: ->
     setupObservers true

@@ -658,8 +673,16 @@

 # TODO: What happens if this is called multiple times? We should make sure that for each document observrs are made only once
 setupMigrations = ->
-  for document in globals.Document.list
-    document.setupMigrations()
+  updateAll = false
+  # Migrate all except local collections
+  for document in globals.Document.list when document.Meta.collection._name isnt null
+    # Always run setupMigrations, don't short circuit
+    updateAll = document.setupMigrations() or updateAll
+
+  if updateAll
+    Log.warn "Migrations requested updating all references..."
+    globals.Document.updateAll()
+    Log.warn "Done."

 migrations = ->
   if globals.Document.Migrations.find({}, limit: 1).count() == 0


Bugfix.

Bugfix.

--- server.coffee
+++ server.coffee
@@ -368,10 +368,8 @@

       query =
         _id:
-          $nin:
-            ids
-      query[field.reverseName] =
-        _id: id
+          $nin: ids
+      query["#{ field.reverseName }._id"] = id

       update = {}
       update[field.reverseName] =


Making installing callbacks better.

Making installing callbacks better.

--- server.coffee
+++ server.coffee
@@ -38,44 +38,47 @@
     oldDocuments = {}
     documents = {}

-    publish._setAfterFlush = (computation) ->
-      return if computation._publishAfterFlushSet
-      computation._publishAfterFlushSet = true
+    publish._installCallbacks = (computation) ->
+      unless computation._publishOnStopSet
+        computation._publishOnStopSet = true

-      # We cannot really use Tracker.afterFlush here because we have to make sure afterFlush callback is called in
-      # the same fiber as current computation is being run. Otherwise if the current computation calls observeChanges,
-      # observeChanges callbacks are called in a different fiber, so when those callbacks call "added" publish method,
-      # it is as well called in a different fiber, and when that method calls "_setAfterFlush" method, Tracker.afterFlush
-      # would be called in a different fiber as well, which is not the same as the computation which is being run is in.
-      # This makes afterFlush be called every time after every "added" method call, instead after the whole current
-      # computation (because those are two different fibers) which breaks the behavior of the afterFlush callback which
-      # expects to be run after the whole computation finishes.
-      computation._trackerInstance.afterFlushCallbacks.push =>
-        # We remove those which are not published anymore.
-        for collectionName of @_documents
-          currentlyPublishedDocumentIds = _.keys(@_documents[collectionName] or {})
-          currentComputationAddedDocumentIds = _.keys(documents[computation._id]?[collectionName] or {})
-          # If afterFlush for other autoruns in the publish function have not yet run, we have to look in "documents" as well.
-          otherComputationsAddedDocumentsIds = _.union (_.keys(docs[collectionName] or {}) for computationId, docs of documents when computationId isnt "#{computation._id}")...
-          # But after afterFlush, "documents" is empty to be ready for next rerun of the computation, so we look into "oldDocuments".
-          otherComputationsPreviouslyAddedDocumentsIds = _.union (_.keys(docs[collectionName] or {}) for computationId, docs of oldDocuments when computationId isnt "#{computation._id}")...
+        computation.onStop =>
+          delete oldDocuments[computation._id]
+          delete documents[computation._id]

-          # We ignore IDs found in both otherComputationsAddedDocumentsIds and otherComputationsPreviouslyAddedDocumentsIds
-          # which might ignore more IDs then necessary (an ID might be previously added which has not been added in this
-          # iteration) but this is OK because in afterFlush of other computations this will be corrected and documents
-          # with those IDs removed.
-          for id in _.difference currentlyPublishedDocumentIds, currentComputationAddedDocumentIds, otherComputationsAddedDocumentsIds, otherComputationsPreviouslyAddedDocumentsIds
-            @removed collectionName, id
+      unless computation._publishAfterFlushSet
+        computation._publishAfterFlushSet = true

-        oldDocuments[computation._id] = documents[computation._id] or {}
-        documents[computation._id] = {}
-        computation._publishAfterFlushSet = false
+        # We cannot really use Tracker.afterFlush here because we have to make sure afterFlush callback is called in
+        # the same fiber as current computation is being run. Otherwise if the current computation calls observeChanges,
+        # observeChanges callbacks are called in a different fiber, so when those callbacks call "added" publish method,
+        # it is as well called in a different fiber, and when that method calls "_installCallbacks" method, Tracker.afterFlush
+        # would be called in a different fiber as well, which is not the same as the computation which is being run is in.
+        # This makes afterFlush be called every time after every "added" method call, instead after the whole current
+        # computation (because those are two different fibers) which breaks the behavior of the afterFlush callback which
+        # expects to be run after the whole computation finishes.
+        computation._trackerInstance.afterFlushCallbacks.push =>
+          # We remove those which are not published anymore.
+          for collectionName of @_documents
+            currentlyPublishedDocumentIds = _.keys(@_documents[collectionName] or {})
+            currentComputationAddedDocumentIds = _.keys(documents[computation._id]?[collectionName] or {})
+            # If afterFlush for other autoruns in the publish function have not yet run, we have to look in "documents" as well.
+            otherComputationsAddedDocumentsIds = _.union (_.keys(docs[collectionName] or {}) for computationId, docs of documents when computationId isnt "#{computation._id}")...
+            # But after afterFlush, "documents" is empty to be ready for next rerun of the computation, so we look into "oldDocuments".
+            otherComputationsPreviouslyAddedDocumentsIds = _.union (_.keys(docs[collectionName] or {}) for computationId, docs of oldDocuments when computationId isnt "#{computation._id}")...

-      computation.onStop =>
-        delete oldDocuments[computation._id]
-        delete documents[computation._id]
+            # We ignore IDs found in both otherComputationsAddedDocumentsIds and otherComputationsPreviouslyAddedDocumentsIds
+            # which might ignore more IDs then necessary (an ID might be previously added which has not been added in this
+            # iteration) but this is OK because in afterFlush of other computations this will be corrected and documents
+            # with those IDs removed.
+            for id in _.difference currentlyPublishedDocumentIds, currentComputationAddedDocumentIds, otherComputationsAddedDocumentsIds, otherComputationsPreviouslyAddedDocumentsIds
+              @removed collectionName, id

-      computation._trackerInstance.requireFlush()
+          oldDocuments[computation._id] = documents[computation._id] or {}
+          documents[computation._id] = {}
+          computation._publishAfterFlushSet = false
+
+        computation._trackerInstance.requireFlush()

     originalAdded = publish.added
     publish.added = (collectionName, id, fields) ->
@@ -88,7 +91,7 @@
         # from the observeChanges callback from an observeChanges called inside a reactive context.
         currentComputation = Fiber.current._publishComputation
       if currentComputation
-        @_setAfterFlush currentComputation
+        @_installCallbacks currentComputation
         Meteor._ensure(documents, currentComputation._id, collectionName)[stringId] = true

       # If document as already present in publish then we call changed to send updated fields (Meteor sends only a diff).
@@ -116,7 +119,7 @@
       else
         currentComputation = Fiber.current._publishComputation
       if currentComputation
-        @_setAfterFlush currentComputation
+        @_installCallbacks currentComputation

       # Mark it as ready only the first time.
       originalReady.call @ unless ready


fix healthcheck

fix healthcheck

--- server.coffee
+++ server.coffee
@@ -13,20 +13,20 @@
   protocol: MESHBLU_PROTOCOL

 app = express()
-app.use meshbluAuth(
+app.use (request, response, next) ->
+  if request.path == '/healthcheck' then return response.send({online: true})
+  next()
+
+app.use meshbluAuth()
   server: MESHBLU_HOST
   port: MESHBLU_PORT
   protocol: MESHBLU_PROTOCOL
-)
-
-app.get '/healthcheck', (req, res) ->
-  res.send online: true

 app.get '/triggers', triggerController.getTriggers

 app.post '/trigger/:flowId/:triggerId', triggerController.trigger

-server = app.listen TRIGGER_SERVICE_PORT, =>
+server = app.listen TRIGGER_SERVICE_PORT, ->
   host = server.address().address
   port = server.address().port


svu-helper for db name

svu-helper for db name

--- server.coffee
+++ server.coffee
@@ -2,7 +2,7 @@
 app = express()

 mongoose = require('mongoose')
-mongoConnectionString = process.env.MONGO_URL || 'mongodb://localhost/svu-helpers'
+mongoConnectionString = process.env.MONGO_URL || 'mongodb://localhost/svu-helper'
 mongoose.connect(mongoConnectionString)

 jsonMiddleware = require('body-parser').json()


Commented on socket tests.

Commented on socket tests.

--- tests/sockets.coffee
+++ tests/sockets.coffee
@@ -1,8 +1,5 @@
 zappa = require '../src/zappa'
 port = 15700
-
-# socket.io-client currently (2011-11-22) not working in node 0.6.x
-# https://github.com/LearnBoost/socket.io-client/issues/336

 @tests =
   connects: (t) ->
@@ -36,7 +33,7 @@

     zapp = zappa port++, ->
       @on shout: ->
-        @io.sockets.emit 'shout', @data
+        @emit 'shout', @data

     c = t.client(zapp.app)
     c.connect()
@@ -48,11 +45,12 @@
     c.on 'shout', (data) ->
       t.reached 'reached1'
       t.equal 'data1', data.foo, 'bar'
-
+
+    # FIXME c2 should not be receiving messages!
     c2.on 'shout', (data) ->
       t.reached 'reached2'
       t.equal 'data2', data.foo, 'bar'
-
+
     c.emit 'shout', foo: 'bar'

   'server ack': (t) ->


Don't load chokidar until absolutely required.

Seems to cause random failures.

--- lib/analysis_component.coffee
+++ lib/analysis_component.coffee
@@ -1,7 +1,6 @@
 AnalysisServer = require './analysis_server'
 AnalysisView = require './views/analysis_view'

-chokidar = require 'chokidar'
 spawn = require('child_process').spawn
 extname = require('path').extname

@@ -10,6 +9,7 @@
   subscriptions: []
   analysisStatusView: null
   analysisView: null
+  analysisServer: null

   enable: =>
     @subscriptions.push atom.project.on 'path-changed', @watchDartProject
@@ -28,6 +28,8 @@
     @cleanup()

     return unless @isDartProject()
+
+    chokidar = require 'chokidar'

     rootPath = atom.project.getPath()
     @analysisServer = new AnalysisServer(rootPath)


GitHub: modal: trigger windowresize even if an error occurs

--- client/Main/extras/github/views/githubmodal.coffee
+++ client/Main/extras/github/views/githubmodal.coffee
@@ -64,10 +64,12 @@
           An error occured while fetching
           repos from #{GitHub.makeLink username}...
         """
+        new KDNotificationView title: err.message  if err.message?
+
+        KD.utils.defer =>
+          @_windowDidResize()
+
         return warn err
-
-      # a = new LocalStorage 'kithub', '1.0'
-      # log repos = a.getAt 'repos'

       if @repoFilter?
         repos = @repoFilter repos


activity: render name instead of purpose for privatechannels

--- client/activity/lib/util/prepareThreadTitle.coffee
+++ client/activity/lib/util/prepareThreadTitle.coffee
@@ -6,10 +6,10 @@
 module.exports = prepareThreadTitle = (thread) ->

   channel = thread.get 'channel'
-  purpose = channel.get 'purpose'
+  name = channel.get 'name'

-  if purpose
-    return {purpose}
+  if name
+    return {name}

   preview = channel.get 'participantsPreview'
   count   = channel.get 'participantCount'


no-refresh: refresh the vm list when the account is changed

--- client/app/MainApp/VirtualizationController.coffee
+++ client/app/MainApp/VirtualizationController.coffee
@@ -7,8 +7,12 @@
     @dialogIsOpen = no
     @resetVMData()

-    (KD.getSingleton 'mainController').once 'AppIsReady', => @fetchVMs()
+    KD.getSingleton('mainController')
+      .once('AppIsReady', @bound 'fetchVMs')
+      .on('AccountChanged', => @emit 'VMListChanged')
+
     @on 'VMListChanged', @bound 'resetVMData'
+

   run:(options, callback = noop)->
     [callback, options] = [options, callback]  unless callback


client/util: don't show managed stack if no machines

Signed-off-by: Umut Sirin <4055abbc0e40a1254e715b952ccd414607e0b365@gmail.com>

--- client/app/lib/util/calculateOwnedResources.coffee
+++ client/app/lib/util/calculateOwnedResources.coffee
@@ -28,7 +28,7 @@
     s.title.indexOf('Managed VMs') > -1

   # and finally show managed machines on bottom.
-  if managedStack
+  if managedStack and managedStack.machines?.length
     resources.push { stack: managedStack, template: null, unreadCount: 0 }

   debug 'owned resources are calculated', resources


fix: grunt watch task

fix: grunt watch task

--- build/config/watch.coffee
+++ build/config/watch.coffee
@@ -7,7 +7,7 @@
     ]
     tasks: [
       "coffee:dist"
-      "coffee:test"
+      "coffee:testIntegration"
       "ngtemplates"
       "timestamp"
     ]


Fixed improper reference

Fixed improper reference

--- slider.coffee
+++ slider.coffee
@@ -137,7 +137,7 @@

 					# Keyboard
-					if arrowkeys
+					if settings.arrowkeys
 						$(document).keydown (event) ->
 							switch event.which
 								when 39


FIX: flex layout with margins works now - all 667 tests passing

--- source/Art/Engine/Core/EpochLayout/FlexLayout.coffee
+++ source/Art/Engine/Core/EpochLayout/FlexLayout.coffee
@@ -110,7 +110,7 @@

       margin = layoutMargin child, elementSizeForChildren
       if i > 0
-        effectivePrevMargin = max lastChildsNextMargin, margin[previousMargin]
+        totalMainSize += effectivePrevMargin = max lastChildsNextMargin, margin[previousMargin]
         spaceForFlexChildren -= effectivePrevMargin

       lastChildsNextMargin = margin[nextMargin]


SessionController: Renamed the method sessionExists to checkSession for clarity.

--- helpers/session.coffee
+++ helpers/session.coffee
@@ -14,7 +14,7 @@
 		@url ?= ''
 		@globalEvents = false

-	@sessionExists: ->
+	@checkSession: ->

 		ajaxProperties =
 			xhrFields:


return existing user

return existing user

--- server/rest.coffee
+++ server/rest.coffee
@@ -13,9 +13,13 @@
     post: (data) ->
       @setContentType "application/json"
       data = bufferToJSON(data)
+
       unless @query.authToken and @query.authToken is process.env["REST_SECRET_KEY"]
         @setStatusCode 401
         return EJSON.stringify(message: "Unauthorized. Give a valid authToken as a parameter.")
-      return EJSON.stringify(message: "No user created! User with email " + data.email + " already exists.")  if Meteor.users.findOne(email: data.email)
+
+      if existingUser = Meteor.users.findOne(email: data.email)
+        return EJSON.stringify existingUser
+
       id = Accounts.createUser(data)
       EJSON.stringify Meteor.users.findOne(id)


fixed config to work with s3 correctly

fixed config to work with s3 correctly

--- config/settings.defaults.coffee
+++ config/settings.defaults.coffee
@@ -10,11 +10,8 @@
 	mongo:
 		url: 'mongodb://127.0.0.1/sharelatex'

-	#filestore:
-	#	backend: "s3"
-	#	stores:
-	#		user_files: ""
+	#docstore:
 	#	s3:
 	#		key: ""
 	#		secret: ""
-	#	fail: false+	#		bucket: "something"


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/bounded.coffee
+++ test/bounded.coffee
@@ -27,7 +27,7 @@
   equals bounds.xw, 100 / 2
   equals bounds.yw, 200 / 2

-test "Bounds returns correct x, y when called with offset", ->
+test "Bounds#bounds(width, height) returns correct x, y", ->
   obj = GameObject
     x: -5
     y: 20


Remove unneeded semicolon.

Remove unneeded semicolon.

--- test/browser.coffee
+++ test/browser.coffee
@@ -16,7 +16,7 @@
   mocha.checkLeaks()

   # Needs to run once this file is loaded
-  window.sepia.start();
+  window.sepia.start()

   if window.mochaPhantomJS
     mochaPhantomJS.run()


evaluator: remove console log

evaluator: remove console log

--- src/evaluator.coffee
+++ src/evaluator.coffee
@@ -135,7 +135,6 @@

 evaluate = (formula, data) ->
   f = factory.build formula
-  console.log(f)
   f.evaluate data

 module.exports = evaluate


quifix Travis 2

quifix Travis 2

--- test/helpers.coffee
+++ test/helpers.coffee
@@ -17,6 +17,7 @@
 # will create @app in context
 # usage : before helpers.init require '../server'
 helpers.init = (instantiator) -> (done) ->
+    this.timeout 5000
     @app = instantiator()
     @app.compound.on 'models', (models) ->
         {Application, CozyInstance, User, Notification} = models


Update prompt tests to use new error message.

--- test/prompts.coffee
+++ test/prompts.coffee
@@ -8,13 +8,13 @@
     tests = promptTests 'sample',
       expectedPrompt: "\x1b[42m\x1b[37m user@host \x1b[0m\x1b[44m\x1b[37m ~/path/to/impromptu \x1b[0m"

-  describe 'Compiler Error', ->
+  describe 'CoffeeScript Compilation Error', ->
     tests = promptTests 'compiler-error',
       expectedError:
         name: 'compiler error'
         text: 'Your prompt file is not valid CoffeeScript.'

-  describe 'JavaScript Loading Error', ->
+  describe 'JavaScript Prompt Method Error', ->
     tests = promptTests 'load-error',
       expectedError:
         name: 'runtime error'


improved docstore acceptence tests to deal with changing docs on get all docs

--- test/acceptance/coffee/GettingAllDocsTests.coffee
+++ test/acceptance/coffee/GettingAllDocsTests.coffee
@@ -11,22 +11,25 @@
 		@project_id = ObjectId()
 		@docs = [{
 			_id: ObjectId()
-			lines: ["one"]
+			lines: ["one", "two", "three"]
 			rev: 2
 		}, {
 			_id: ObjectId()
-			lines: ["two"]
+			lines: ["aaa", "bbb", "ccc"]
 			rev: 4
 		}, {
 			_id: ObjectId()
-			lines: ["three"]
+			lines: ["111", "222", "333"]
 			rev: 6
 		}]
 		DocstoreClient.createProject @project_id, (error) =>
 			throw error if error?
 			jobs = for doc in @docs
 				do (doc) =>
-					(callback) => DocstoreClient.createDoc @project_id, doc._id, doc.lines, callback
+					(callback) =>
+						DocstoreClient.createDoc @project_id, doc._id, doc.lines, (err)=>
+							doc.lines[0] = doc.lines[0]+" added"
+							DocstoreClient.updateDoc @project_id, doc._id, doc.lines, callback
 			async.series jobs, done

 	afterEach (done) ->
@@ -40,3 +43,4 @@
 				doc.lines.should.deep.equal @docs[i].lines
 			done()

+


Date added to the chat messages

Date added to the chat messages

--- main/static/src/coffee/site/chat.coffee
+++ main/static/src/coffee/site/chat.coffee
@@ -24,10 +24,11 @@

 window.on_chat_message_received = (message) ->
   time = new Date(message.time)
+  datestamp = "#{time.getFullYear()}-#{leading_zero(time.getMonth())}-#{leading_zero(time.getDate())}"
   timestamp = "#{leading_zero(time.getHours())}:#{leading_zero(time.getMinutes())}:#{leading_zero(time.getSeconds())}"
   $('.chat-log').prepend """

-
#{message.name}  @ #{timestamp}

+
#{message.name} #{datestamp} @ #{timestamp}

#{message.message}

   """


Add more readability

Add more readability

--- src/assets/scripts/coffee/onload/smoothScroll.coffee
+++ src/assets/scripts/coffee/onload/smoothScroll.coffee
@@ -1,68 +1,62 @@
 smoothScroll = ->

+  if (!document.documentElement) then return else $html  = document.documentElement
+  if (!document.body)            then return else $body  = document.body
+  if (!document.links.length)    then return else $links = document.links
+
   options =
-    totalTime: 500
-    refreshRate: 15
-    disableClass: '.js--smoothScroll--off'
+    totalTime    : 500
+    refreshRate  : 15
+    disableClass : '.js--smoothScroll--off'

-  links = document.links
-  if (!links.length) then return
+  clicked = (e)->

-  scroll = (e)->
-
+    $link = this
     e.preventDefault()

-    touch = (e)->
+    ignoreTouch = (e)->
       e.preventDefault()
       return
-    body = document.body
-    if (!body) then return
-    body.addEventListener('touchstart', touch, false)
+    $body.addEventListener('touchstart', ignoreTouch, false)

-    windwowH = window.innerHeight
-    documentH = document.documentElement.scrollHeight
-    scrollable = documentH - windwowH
-    if (scrollable < 0) then return
+    id = $link.hash.replace('#', '').replace(/\?.*/, '')
+    $target = document.getElementById(id)

-    scrolled = -> return (document.documentElement.scrollTop || body.scrollTop)
+    targetScrollTop = (elem)->
+      px = 0
+      while(elem)
+        px  += elem.offsetTop || 0
+        elem = elem.offsetParent
+      return px

-    target = this.hash.replace('#', '').replace(/\?.*/, '')
-    target = document.getElementById(target)
-
-    scaleFromTop = (x)->
-      n = 0
-      while(x)
-        n += x.offsetTop || 0
-        x = x.offsetParent
-      return n
-    goal = scaleFromTop(target)
+    goal = targetScrollTop($target)
+    if (goal > $html.scrollHeight - window.innerHeight) then goal = $html.scrollHeight - window.innerHeight
     if (goal < 0) then goal = 0
-    if (goal > scrollable) then goal = scrollable

     equation = (a, b, c, d)-> return a * b / c + d
-
+    currentScrollTop = -> return ($html.scrollTop || $body.scrollTop)
     startTime = new Date()

-    run = ->
+    scroll = ->
       lapseTime = new Date() - startTime
-      oneCycle = equation((goal - scrolled()), lapseTime, options.totalTime, scrolled())
+      oneStep = equation((goal - currentScrollTop()), lapseTime, options.totalTime, currentScrollTop())
       if (options.totalTime > lapseTime + options.refreshRate)
-        window.scrollTo(0, parseInt(oneCycle))
-        setTimeout(run, options.refreshRate)
+        window.scrollTo(0, parseInt(oneStep))
+        setTimeout(scroll, options.refreshRate)
       else
         window.scrollTo(0, parseInt(goal))
-        body.removeEventListener('touchstart', touch, false)
+        $body.removeEventListener('touchstart', ignoreTouch, false)
       return
-    run()
+    scroll()

     return

-  for link in links by -1
-    if (link.getAttribute('href').substring(0, 1) isnt '#') then continue
-    if (link.className.indexOf(options.disableClass.replace('.', '')) >= 0) then continue
-    id = link.hash.replace('#', '').replace(/\?.*/, '')
+  for $link in $links by -1
+    if ($link.getAttribute('href').substring(0, 1) isnt '#') then continue
+    if ($link.className.indexOf(options.disableClass.replace('.', '')) >= 0) then continue
+    id = $link.hash.replace('#', '').replace(/\?.*/, '')
     if (id && document.getElementById(id))
-      link.addEventListener('click', scroll, false)
+      $link.addEventListener('click', clicked, false)

   return


Fix formatting

Fix formatting

--- framer/Layer.coffee
+++ framer/Layer.coffee
@@ -58,9 +58,8 @@
 				if name is cssProperty and not LayerStyle[cssProperty]?
 					mainElement?.style[cssProperty] = @_properties[name]
 					subElement?.style[cssProperty] = @_properties[name]
-				else if not @__applyingDefaults
-						# These values are set multiple times during applyDefaults, so ignore them here, and set the style in the constructor
-						or (cssProperty not in ["webkitTransform", "webkitFilter", "webkitPerspectiveOrigin", "webkitTransformOrigin"])
+				# These values are set multiple times during applyDefaults, so ignore them here, and set the style in the constructor
+				else if not @__applyingDefaults or (cssProperty not in ["webkitTransform", "webkitFilter", "webkitPerspectiveOrigin", "webkitTransformOrigin"])
 					style = LayerStyle[cssProperty](@)
 					mainElement?.style[cssProperty] = style
 					subElement?.style[cssProperty] = style


Don't do reset before the dom is ready

Don't do reset before the dom is ready

--- framer/Utils.coffee
+++ framer/Utils.coffee
@@ -4,23 +4,27 @@
 Utils = {}

 Utils.reset = ->
-	Utils.domComplete ->
-
-		for layer in Session._LayerList
-			layer.removeAllListeners()
-
-		Session._LayerList = []
-		Session._RootElement?.innerHTML = ""
-
-		if Session._delayTimers
-			for delayTimer in Session._delayTimers
-				clearTimeout delayTimer
-			Session._delayTimers = []
-
-		if Session._delayIntervals
-			for delayInterval in Session._delayIntervals
-				clearInterval delayInterval
-			Session._delayIntervals = []
+
+	# There is no use calling this even before the dom is ready
+	if __domReady is false
+		return
+
+	# Remove all the listeners so we don't leak memory
+	for layer in Session._LayerList
+		layer.removeAllListeners()
+
+	Session._LayerList = []
+	Session._RootElement?.innerHTML = ""
+
+	if Session._delayTimers
+		for delayTimer in Session._delayTimers
+			clearTimeout delayTimer
+		Session._delayTimers = []
+
+	if Session._delayIntervals
+		for delayInterval in Session._delayIntervals
+			clearInterval delayInterval
+		Session._delayIntervals = []

 Utils.getValue = (value) ->
 	return value() if _.isFunction value
@@ -263,10 +267,12 @@
 # DOM FUNCTIONS

 __domComplete = []
+__domReady = false

 if document?
 	document.onreadystatechange = (event) =>
 		if document.readyState is "complete"
+			__domReady = true
 			while __domComplete.length
 				f = __domComplete.shift()()


Allow numbers in GitHub usernames.

Allow numbers in GitHub usernames.

--- src/scripts/github-credentials.coffee
+++ src/scripts/github-credentials.coffee
@@ -17,7 +17,7 @@

     msg.send theReply

-  robot.respond /i am ([a-z-]+)/i, (msg) ->
+  robot.respond /i am ([a-z0-9-]+)/i, (msg) ->
     githubLogin = msg.match[1]
     msg.message.user.githubLogin = githubLogin
     msg.send "Ok, you are " + githubLogin + " on GitHub"


Update user state in the brain to use userForName / userForId to allow data persistence

--- src/hubot/irc.coffee
+++ src/hubot/irc.coffee
@@ -63,18 +63,18 @@

     bot.addListener 'message', (from, to, message) ->
       console.log "From #{from} to #{to}: #{message}"
+
+      user = self.userForName from
+      unless user?
+        id = (new Date().getTime() / 1000).toString().replace('.','')
+        user = self.userForId id
+        user.name = from

-      if message.match new RegExp "^#{options.nick}", "i"
-        unless user_id[from]
-          user_id[from] = next_id
-          next_id = next_id + 1
-
-      user = new Robot.User user_id[from]
-      user.name = from
       if to.match(/^[&#]/)
         user.room = to
         console.log "#{to} <#{from}> #{message}"
       else
+        user.room = null
         console.log "msg <#{from}> #{message}"

       self.receive new Robot.TextMessage(user, message)


fix a traceback error saw in hub logs...

--- src/smc-hub/postgres-synctable.coffee
+++ src/smc-hub/postgres-synctable.coffee
@@ -378,7 +378,9 @@
                             continue
                         else
                             for collab_account_id in a.users
-                                @_add_user_to_project(collab_account_id, a.project_id)
+                                # NOTE: Very rarely, sometimes collab_account_id is not defined
+                                if collab_account_id?
+                                    @_add_user_to_project(collab_account_id, a.project_id)
                 # call the callbacks
                 if err
                     dbg("error registering '#{account_id}' -- err=#{err}")


jupyter: temporary fix in case anybody is seeing "file is currently being read or written"

--- src/smc-webapp/jupyter/actions.coffee
+++ src/smc-webapp/jupyter/actions.coffee
@@ -493,7 +493,12 @@
                     if @_syncdb_cell_change(key.get('id'), record)
                         cell_list_needs_recompute = true
                 when 'fatal'
-                    @setState(fatal: record?.get('error'))
+                    error = record?.get('error')
+                    @setState(fatal: error)
+                    # This check can be deleted in a few weeks:
+                    if error? and error.indexOf('file is currently being read or written') != -1
+                        # No longer relevant -- see https://github.com/sagemathinc/smc/issues/1742
+                        @syncdb.delete(type:'fatal')
                 when 'nbconvert'
                     if @_is_project
                         # before setting in store, let backend react to change


improved code complexity

improved code complexity

--- app/assets/javascripts/annotation_document_manager.coffee
+++ app/assets/javascripts/annotation_document_manager.coffee
@@ -144,28 +144,24 @@

   handleHistoryLegacy: ->
     if this.historyRequest && this.latestSeenDocumentId > 0
-      numberOfShifts = 0
-      for queuedDocument in this.documentStore
-        break if queuedDocument && queuedDocument.id == this.latestSeenDocumentId
-        numberOfShifts++
-      this.documentStore.shift() for x in [1..numberOfShifts] if numberOfShifts > 0
+      idList = (item.id for item in this.documentStore)
+      for documentId in idList
+        break if documentId == this.latestSeenDocumentId
+        this.documentStore.shift()

     this.historyRequest = false

   documentIdFromUrl: ->
-    pathname = document.location.pathname
-    regex = /.*\/annotate\/([0-9]+)\D*/
-    result = regex.exec(pathname)
-    return result unless result
-    result[1]
+    regexSearchGroup = /.*\/annotate\/([0-9]+)\D*/.exec(document.location.pathname)
+    return undefined unless regexSearchGroup
+    regexSearchGroup[1]

   rewriteHistory: ->
     replacement = "annotate/#{this.currentDocument.id}"
     newLocationPath = document.location.pathname.replace(/annotate(\/[0-9]+)?/, replacement)
-    newLocationPath += document.location.search if document.location.search

     window.history.pushState { annotationDocumentId: this.currentDocument.id },
                              document.title,
-                             newLocationPath
+                             newLocationPath + document.location.search

 window.AnnotationDocumentManager = AnnotationDocumentManager


Patch handlers not reverted on cancel edit in Leaflet.Draw

--- app/assets/javascripts/leaflet/reactive_measure.js.coffee
+++ app/assets/javascripts/leaflet/reactive_measure.js.coffee
@@ -297,8 +297,18 @@
 #Patch Leaflet.Draw
 L.EditToolbar.Edit.include
   __removeHooks: L.EditToolbar.Edit::removeHooks
-
+  __revertLayer: L.EditToolbar.Edit::_revertLayer
+
+  # Patch missing event
   removeHooks: ->
     @__removeHooks.apply @, arguments
     if @_map
-      @_map.off 'draw:editvertex', @_updateTooltip, @+      @_map.off 'draw:editvertex', @_updateTooltip, @
+
+  # Patch handlers not reverted on cancel edit. See https://github.com/Leaflet/Leaflet.draw/issues/532
+  _revertLayer: (layer) ->
+    id = L.Util.stamp layer
+    @__revertLayer.apply @, arguments
+    layer.editing.latlngs = this._uneditedLayerProps[id].latlngs
+    layer.editing._poly._latlngs = this._uneditedLayerProps[id].latlngs
+    layer.editing._verticesHandlers[0]._latlngs = this._uneditedLayerProps[id].latlngs


OX-746 Test using other byte

OX-746 Test using other byte

--- app/assets/javascripts/oxalis/model/binary/plane2d.coffee
+++ app/assets/javascripts/oxalis/model/binary/plane2d.coffee
@@ -394,7 +394,7 @@

     while i--
       dest = destination.offset++ * bytesDest
-      src = source.offset * bytesSrc
+      src = source.offset * (bytesSrc + 1)
       for t in [1..bytesDest]
         value = source.buffer[src++]
         destination.buffer[dest++] = if contrastCurve? then contrastCurve[value] else value


fix null payment error

fix null payment error

--- app/assets/javascripts/revised/pages/info/payments.coffee
+++ app/assets/javascripts/revised/pages/info/payments.coffee
@@ -66,7 +66,7 @@
       name: 'Bike Index'
       description: description
       amount: amount_cents
-      email: $stripe_form.attr('data-email')
+      email: $stripe_form.data('email')
       allowRememberMe: false
       panelLabel: $stripe_form.data('type')
     return


#1071 Add test

#1071 Add test

--- app/assets/javascripts/test/model/binary/cube.spec.coffee
+++ app/assets/javascripts/test/model/binary/cube.spec.coffee
@@ -133,10 +133,20 @@

     describe "getDataValue()", ->

-      it "should return the correct value", ->
+      it "should return the raw value without a mapping", ->

         value = 1 * (1 << 16) + 2 * (1 << 8) + 3
         cube.labelVoxel([0, 0, 0], value)

         expect(cube.getDataValue([0, 0, 0])).toBe(value)

+      it "should return the mapping value if available", ->
+
+        cube.labelVoxel([0, 0, 0], 42)
+        cube.labelVoxel([1, 1, 1], 43)
+
+        mapping = {42 : 1}
+
+        expect(cube.getDataValue([0, 0, 0], mapping)).toBe(1)
+        expect(cube.getDataValue([1, 1, 1], mapping)).toBe(43)
+


Notification when server find starts

Notification when server find starts

--- client/views/lobby.coffee
+++ client/views/lobby.coffee
@@ -18,6 +18,18 @@
     return if !route?
     if route.route.name isnt "lobby"
       Router.go Router.routes["lobby"].path({id: lobby._id})
+  Deps.autorun -> #Server status change
+    lobby = lobbies.findOne({status: {$ne: null}})
+    return if !lobby?
+    status = lobby.status
+    if status is 1
+      $.pnotify
+        title: "Finding a server"
+        text: "Waiting for an open server slot."
+        type: "info"
+        delay: 5000
+        closer: false
+        sticker: false
   Deps.autorun -> #Chat callbacks
     lobby = lobbies.findOne({status: {$ne: null}})
     if !lobby?


logpage: don't overwrite long text with click

--- lib/actions/views/logs.coffee
+++ lib/actions/views/logs.coffee
@@ -83,6 +83,7 @@
   item.text(new_text)
   item.attr("title", text)
   item.click ->
-    $(this).attr("title", null)
-    $(this).text(this.long_text)
-    this.long_text = null
+    if this.long_text
+      $(this).attr("title", null)
+      $(this).text(this.long_text)
+      this.long_text = null


Remove extra lines (#3963)

Remove extra lines (#3963)

--- test/app/models/CocoModel.spec.coffee
+++ test/app/models/CocoModel.spec.coffee
@@ -41,7 +41,6 @@
       expect(b.getURL()).toBe unprojectedURL + '&project=object,number'

   describe 'save', ->
-
     it 'saves to db/<urlRoot>', ->
       b = new BlandClass({})
       res = b.save()
@@ -195,5 +194,3 @@
       })
       m.updateI18NCoverage()
       expect(_.isEqual(m.get('i18nCoverage'), ['es'])).toBe(true)
-
-


My answer free response label was being set to undefined

--- src/components/task-step/exercise/free-response.cjsx
+++ src/components/task-step/exercise/free-response.cjsx
@@ -12,10 +12,12 @@
     {free_response, student_names} = @props
     FreeResponse = null

-    studentNames = student_names.join(', ') if student_names?
+    freeResponseProps =
+      className: 'free-response'
+    freeResponseProps['data-student-names'] = student_names.join(', ') if student_names?

     if free_response? and free_response.length
-      FreeResponse =

+      FreeResponse =

{free_response}


minor: clean up rewriter generation code

--- coffee/js_rewriter.coffee
+++ coffee/js_rewriter.coffee
@@ -198,21 +198,20 @@
         @rewriteTable = rewriteTable
         @suffixTree = reverseSuffixTable(rewriteTable)

-    generate: ->
+    generateAppendRewriteOnce: ->
         @line("(function(chain, stack )")
         @block =>
-            @line("while( stack.length > 0)")
+            @line "if (stack.length === 0) {throw new Error('empty stack');}"
+            @line("var _e = stack.pop();")
+            @line("var element = _e[0], power = _e[1];")
+            @line("if (chain === null)")
             @block =>
-                @line("var _e = stack.pop();");
-                @line("var element = _e[0], power = _e[1];");
-                @line("if (chain === null)")
-                @block =>
-                    @line("//empty chain")
-                    @line('console.log("Append to empth chain:"+_e);');
-                    @line("var order=(element==='a')?#{@_nodeOrder('a')}:#{@_nodeOrder('b')};")
-                    @line("var lowestPow=(element==='a')?#{@_lowestPower('a')}:#{@_lowestPower('b')};")
-                    @line('chain = new nodeConstructors[element](((power - lowestPow)%order+order)%order+lowestPow, chain);')
-                @generateMain()
+                @line("//empty chain")
+                @line('console.log("Append to empth chain:"+_e);');
+                @line("var order=(element==='a')?#{@_nodeOrder('a')}:#{@_nodeOrder('b')};")
+                @line("var lowestPow=(element==='a')?#{@_lowestPower('a')}:#{@_lowestPower('b')};")
+                @line('chain = new nodeConstructors[element](((power - lowestPow)%order+order)%order+lowestPow, chain);')
+            @generateMain()
             @line("return chain;")
         @line(")")
         return @get()
@@ -377,10 +376,24 @@
         table["original"] = gSuffix
     return revTable

+
+exports.repeatRewrite = repeatRewrite = (appendRewriteOnce) -> (chain, stack) ->
+  while stack.length > 0
+    chain = appendRewriteOnce chain, stack
+  return chain
+
+exports.canAppend = (appendRewriteOnce) -> (chain, element, power) ->
+  stack = [[element, power]]
+  appendRewriteOnce(chain, stack)
+  return stack.length is 0
+
 exports.makeAppendRewrite= makeAppendRewrite = (s)->
   g = new CodeGenerator(s)
   g.debug=false
-  appendRewrite = eval g.generate()
+
+  appendRewriteOnce = eval g.generateAppendRewriteOnce()
+  appendRewrite = repeatRewrite appendRewriteOnce
+
   throw new Error("Failed to compilation gave nothing?") unless appendRewrite?
   return appendRewrite


Stub test and add done callbacks

Stub test and add done callbacks

--- desktop/apps/editorial_features/test/routes.coffee
+++ desktop/apps/editorial_features/test/routes.coffee
@@ -67,49 +67,56 @@
     Backbone.sync.restore()
     done()

-  it 'sets a video index', ->
+  it 'sets a video index', (done) ->
     @req = { params: { slug: 'venice-2' } }
     routes.venice(@req, @res, @next)
     _.defer => _.defer =>
       @res.render.args[0][0].should.equal 'components/venice_2017/templates/index'
       @res.render.args[0][1].videoIndex.should.equal 1
+      done()

-  it 'defaults to the first video', ->
+  it 'defaults to the first video', (done) ->
     @req = { params: { slug: 'blah' } }
     routes.venice(@req, @res, @next)
     _.defer => _.defer =>
       @res.redirect.args[0].should.eql [ 301, '/venice-biennale/toward-venice' ]
+      done()

-  it 'sets a curation', ->
+  it 'sets a curation', (done) ->
     @req = { params: { slug: 'venice' } }
     routes.venice(@req, @res, @next)
     _.defer => _.defer =>
       @res.render.args[0][0].should.equal 'components/venice_2017/templates/index'
       @res.render.args[0][1].curation.get('name').should.eql 'Inside the Biennale'
+      done()

-  it 'sets the 360 video guide', ->
+  it 'sets the 360 video guide', (done) ->
     @req = { params: { slug: 'venice' } }
     routes.venice(@req, @res, @next)
     _.defer => _.defer =>
       @res.render.args[0][1].videoGuide.get('title').should.eql 'Video Guide'
+      done()

-  it 'Fetches sub articles', ->
+  xit 'Fetches sub articles', (done) ->
     @req = { params: { slug: 'venice' } }
     routes.venice(@req, @res, @next)
     _.defer => _.defer =>
       @res.render.args[0][1].sub_articles.length.should.eql 1
+      done()

-  it 'renders json ld', ->
+  it 'renders json ld', (done) ->
     @req = { params: { slug: 'venice' } }
     routes.venice(@req, @res, @next)
     _.defer => _.defer =>
       @res.locals.jsonLD.should.containEql '"headline":"Inside the Biennale venice"'
+      done()

-  it 'includes sailthru', ->
+  it 'includes sailthru', (done) ->
     @req = { params: { slug: 'venice' } }
     routes.venice(@req, @res, @next)
     _.defer => _.defer =>
       @res.locals.sd.INCLUDE_SAILTHRU.should.be.true()
+      done()

 describe 'Vanity route', ->


hardcode extpath

hardcode extpath

--- lib/classpath-provider.coffee
+++ lib/classpath-provider.coffee
@@ -10,7 +10,7 @@

   constructor: (mapping) ->
     @mapping = mapping
-    @mapping.push({name: 'Ext', folder: null})
+    @mapping.push({name: 'Ext', folder: 'ext/src'})

   getSuggestions: ({prefix, editor, bufferPosition, basePath}) ->


Only kill a view binding's view if it was created by it

--- src/view/bindings/view_binding.coffee
+++ src/view/bindings/view_binding.coffee
@@ -13,9 +13,11 @@

     return if not viewClassOrInstance
     if viewClassOrInstance.isView
+      @fromViewClass = false
       @viewInstance = viewClassOrInstance
       @viewInstance.removeFromSuperview()
     else
+      @fromViewClass = true
       @viewInstance = new viewClassOrInstance

     @node.removeAttribute('data-view')
@@ -36,7 +38,7 @@

   die: ->
     @viewInstance.removeFromSuperview()
-    @viewInstance.die()
+    @viewInstance.die() if @fromViewClass
     @superview = null
     @viewInstance = null
     super


Thread through profileStartup load setting

--- src/browser/atom-application.coffee
+++ src/browser/atom-application.coffee
@@ -85,15 +85,16 @@
     else
       @loadState() or @openPath(options)

-  openWithOptions: ({pathsToOpen, urlsToOpen, test, pidToKillWhenClosed, devMode, safeMode, apiPreviewMode, newWindow, specDirectory, logFile}) ->
+  openWithOptions: ({pathsToOpen, urlsToOpen, test, pidToKillWhenClosed, devMode, safeMode, apiPreviewMode, newWindow, specDirectory, logFile, profileStartup}) ->
     if test
       @runSpecs({exitWhenDone: true, @resourcePath, specDirectory, logFile})
     else if pathsToOpen.length > 0
-      @openPaths({pathsToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode, apiPreviewMode})
+      @openPaths({pathsToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode, apiPreviewMode, profileStartup})
     else if urlsToOpen.length > 0
       @openUrl({urlToOpen, devMode, safeMode, apiPreviewMode}) for urlToOpen in urlsToOpen
     else
-      @openPath({pidToKillWhenClosed, newWindow, devMode, safeMode, apiPreviewMode}) # Always open a editor window if this is the first instance of Atom.
+      # Always open a editor window if this is the first instance of Atom.
+      @openPath({pidToKillWhenClosed, newWindow, devMode, safeMode, apiPreviewMode, profileStartup})

   # Public: Removes the {AtomWindow} from the global window list.
   removeWindow: (window) ->
@@ -343,9 +344,10 @@
   #   :devMode - Boolean to control the opened window's dev mode.
   #   :safeMode - Boolean to control the opened window's safe mode.
   #   :apiPreviewMode - Boolean to control the opened window's 1.0 API preview mode.
+  #   :profileStartup - Boolean to control creating a profile of the startup time.
   #   :window - {AtomWindow} to open file paths in.
-  openPath: ({pathToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode, apiPreviewMode, window}) ->
-    @openPaths({pathsToOpen: [pathToOpen], pidToKillWhenClosed, newWindow, devMode, safeMode, apiPreviewMode, window})
+  openPath: ({pathToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode, apiPreviewMode, profileStartup, window}) ->
+    @openPaths({pathsToOpen: [pathToOpen], pidToKillWhenClosed, newWindow, devMode, safeMode, apiPreviewMode, profileStartup, window})

   # Public: Opens multiple paths, in existing windows if possible.
   #
@@ -358,7 +360,7 @@
   #   :apiPreviewMode - Boolean to control the opened window's 1.0 API preview mode.
   #   :windowDimensions - Object with height and width keys.
   #   :window - {AtomWindow} to open file paths in.
-  openPaths: ({pathsToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode, apiPreviewMode, windowDimensions, window}={}) ->
+  openPaths: ({pathsToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode, apiPreviewMode, windowDimensions, profileStartup, window}={}) ->
     pathsToOpen = (fs.normalize(pathToOpen) for pathToOpen in pathsToOpen)
     locationsToOpen = (@locationForPathToOpen(pathToOpen) for pathToOpen in pathsToOpen)

@@ -388,7 +390,7 @@

       bootstrapScript ?= require.resolve('../window-bootstrap')
       resourcePath ?= @resourcePath
-      openedWindow = new AtomWindow({locationsToOpen, bootstrapScript, resourcePath, devMode, safeMode, apiPreviewMode, windowDimensions})
+      openedWindow = new AtomWindow({locationsToOpen, bootstrapScript, resourcePath, devMode, safeMode, apiPreviewMode, windowDimensions, profileStartup})

     if pidToKillWhenClosed?
       @pidsToOpenWindows[pidToKillWhenClosed] = openedWindow


min is 0

min is 0

--- src/indicators/directives/celerity/controller.coffee
+++ src/indicators/directives/celerity/controller.coffee
@@ -15,9 +15,12 @@
       sprintNumbers.push Number sprint.number
       expected.push Number sprintUtils.computeExpectedSpeed sprint
       actual.push Number sprintUtils.computeSpeed sprint
-    console.log [sprintNumbers, expected, actual]
+
     chart = c3.generate
       bindto: '#celerity-cart'
       data:
         x: 'sprint'
         columns: [sprintNumbers, expected, actual]
+      axis:
+        y:
+          min: 0


Fix: Pass "null" option from .belongsTo through to uuid property.

--- lib/ST/Model/Base.coffee
+++ lib/ST/Model/Base.coffee
@@ -359,7 +359,7 @@
       }

     @classMethod 'belongsTo', (name, options={}) ->
-      @string "#{name}Uuid"
+      @string "#{name}Uuid", null: options.null

       ucName = ST.ucFirst name
       options.model ||= ucName


ensure isinteractive() is true

ensure isinteractive() is true

--- lib/connection/process.coffee
+++ lib/connection/process.coffee
@@ -110,7 +110,7 @@
         return
       else
         @emitter.emit 'stdout', "PowerShell version < 3 encountered. Running without wrapper (interrupts won't work)."
-    @proc = child_process.spawn(@jlpath(), [@script("boot.jl"), port], cwd: @workingDir())
+    @proc = child_process.spawn(@jlpath(), ["-i", @script("boot.jl"), port], cwd: @workingDir())
     fn()

   getFreePort: (fn) ->


showing plots when new plots come in

showing plots when new plots come in

--- bokeh/server/static/coffee/usercontext/usercontext.coffee
+++ bokeh/server/static/coffee/usercontext/usercontext.coffee
@@ -46,10 +46,22 @@
     class : 'usercontext'
   events :
     'click .bokehrefresh' : () -> @collection.fetch({update:true})
+
   delegateEvents : (events) ->
     super(events)
     @listenTo(@collection, 'add', @render)
     @listenTo(@collection, 'remove', @render)
+    @listenTo(@collection, 'add', (model, collection, options) =>
+      @listenTo(model, 'loaded', () =>
+        @listenTo(model.get_obj('plot_context'), 'change', () =>
+          @trigger('show')
+        )
+      )
+    )
+    @listenTo(@collection, 'remove', (model, collection, options) =>
+      @stopListening(model)
+    )
+
   render : ->
     html = userdocstemplate()
     _.map(_.values(@views), (view) -> view.$el.detach())


Do not use jQuery#offset() to determie dropdown position

--- vendor/assets/javascripts/chosen/chosen.js.coffee
+++ vendor/assets/javascripts/chosen/chosen.js.coffee
@@ -349,20 +349,13 @@
   update_dropdown_position: ->
     return unless @opened

-    offsets = @$container.offset()
     rect = @$container[0].getBoundingClientRect()
     border_width = parseInt(@$container.css("border-bottom-width"))
-
-    if rect.width
-      width = rect.width
-      height = rect.height
-    else
-      width = rect.right - rect.left
-      height = rect.bottom - rect.top
+    width = if rect.width then rect.width else rect.right - rect.left

     @$dropdown.css
-      left: "#{offsets.left}px",
-      top: "#{offsets.top + height - border_width}px"
+      left: "#{rect.left}px",
+      top: "#{rect.bottom - border_width}px"
       width: "#{width}px"

     return


Fix up the message sent back with the status

--- scripts/pr-status.coffee
+++ scripts/pr-status.coffee
@@ -29,4 +29,4 @@
     github.get pull_url, (pull) ->
       github.get pull.statuses_url, (status) ->
         last_status = status[0]
-        msg.send "#{pull.html_url} - #{last_status.state} - #{last_status.description}"
+        msg.send "#{last_status.state} - #{last_status.target_url}"


fixed it so private messages dont count at all

--- scripts/sentiment.coffee
+++ scripts/sentiment.coffee
@@ -125,8 +125,8 @@
   logSentiment = (response) ->
     analysis = sentiment(response.message.text)
     if(response.message.text.length > 2 && analysis && analysis.score)
-      updateEntry('user', getUsername(response), getWeekOfYear(), analysis.score)
       if !isPrivateMessage(response)
+        updateEntry('user', getUsername(response), getWeekOfYear(), analysis.score)
         updateEntry('channel', getChannel(response), getWeekOfYear(), analysis.score)

   prettyPrintList = (entries, emptyMessage) ->


support Math

support Math

--- lib/markdown-convert.coffee
+++ lib/markdown-convert.coffee
@@ -225,6 +225,24 @@
               .join('\n')
     callback lines, imagePaths

+processMath = (text)->
+  text = text.replace(/\\\$/g, '#slash_dollarsign#')
+
+  # display
+  text = text.replace /\$\$([\s\S]+?)\$\$/g, ($0, $1)->
+    $1 = $1.replace(/\n/g, '').replace(/\#slash\_dollarsign\#/g, '\\\$')
+    $1 = escape($1)
+    "

"
+
+  # inline
+  r = /\$([\s\S]+?)\$/g
+  text = text.replace /\$([\s\S]+?)\$/g, ($0, $1)->
+    $1 = $1.replace(/\n/g, '').replace(/\#slash\_dollarsign\#/g, '\\\$')
+    $1 = escape($1)
+    ""
+
+  text = text.replace(/\#slash\_dollarsign\#/g, '\\\$')
+  text

 # convert relative path to project path
 processPaths = (text, rootDirectoryPath, projectDirectoryPath)->
@@ -275,6 +293,8 @@
   # this is actually differnet from pandoc-wrapper.coffee
   text = processPaths text, rootDirectoryPath, projectDirectoryPath

+  text = processMath text
+
   # TODO: create imageFolder
   if config['image_dir'][0] == '/'
     imageDirectoryPath = path.resolve(projectDirectoryPath, '.' + config['image_dir'])


remove old color config correctly

remove old color config correctly

--- src/Ableton.coffee
+++ src/Ableton.coffee
@@ -17,7 +17,7 @@
         @lastMessage = message
       else
         message = [@lastMessage[0], @lastMessage[1], _.last message]
-
+
       @pushWrapper.receive_midi message

     @midiInput.openPort 0
@@ -33,7 +33,7 @@

   setButtonColor:({x,y,color}) =>
-    @buttons = _.reject @buttons, {x,y,color}
+    @buttons = _.reject @buttons, {x,y}
     @buttons.push {x,y,color}
     @_setButtonColors()


if a server side template doesn't exist, return an error

--- server/code/index.coffee
+++ server/code/index.coffee
@@ -447,6 +447,8 @@

 renderServerAndClientSide = (options, req, resp) ->
   fs.readFile "client/template/#{options.page}.eco", (err, contentTemplate) ->
+    if err?
+      resp.send 500, {error: "Template not found: #{err}"}
     _.extend options, pageTitles.SubNav[options.page]
     options.subnav ?= 'subnav'
     fs.readFile "client/template/#{options.subnav}.eco", (err, subnavTemplate) ->


remove an obsolete comment

remove an obsolete comment

--- src/kite/kite.coffee
+++ src/kite/kite.coffee
@@ -103,7 +103,7 @@
   getKiteInfo: (params) ->
     username    : "#{ @options.username ? 'anonymous' }"
     environment : "#{ @options.environment ? 'browser-environment' }"
-    name        : "#{ params?[0]?.kiteName ? @options.name ? 'browser-kite' }" # TODO: don't know where to get this value for now
+    name        : "#{ params?[0]?.kiteName ? @options.name ? 'browser-kite' }"
     version     : "#{ @options.version ? '1.0.0' }"
     region      : "#{ @options.region ? 'browser-region' }"
     hostname    : "#{ @options.hostname ? 'browser-hostname' }"


Use register as an argument and exec as a static method

--- index.coffee
+++ index.coffee
@@ -28,20 +28,20 @@
     STATUS_CODE_MAP[status.code]?.desc is desc

-module.exports = (Impromptu, git) ->
+module.exports = (Impromptu, register, git) ->
   # Expose the repo object from the git-utils library
   # This will be null when we're not in a repo
-  @register '_repo',
+  register '_repo',
     update: (done) ->
       done null, repo

   # Helper to figure out if we're in a repo at all
-  @register 'isRepo',
+  register 'isRepo',
     update: (done) ->
       done null, !! repo

   # Root path to the repository
-  @register 'root',
+  register 'root',
     update: (done) ->
       root = repo?.getPath().replace /\.git\/$/, ''
       done null, root
@@ -49,14 +49,14 @@
   # Branch name
   # Returns commit hash when head is detached
   # Returns null in newly-initialized repos (note that isRepo() still returns true in this case)
-  @register 'branch',
+  register 'branch',
     update: (done) ->
       branch = repo?.getShortHead()
       done null, branch

   # Determine whether the repo is currently in a detached head state
   # This happens when you checkout, for example, a commit hash
-  @register 'isDetachedHead',
+  register 'isDetachedHead',
     update: (done) ->
       branch = repo?.getHead()
       done null, ! /^refs\/heads\//.test branch
@@ -65,18 +65,18 @@
   # Each has a count of commits that your repo is ahead/behind its upstream
   #
   # This command *must* be passed through a formatter before its displayed
-  @register '_aheadBehind',
+  register '_aheadBehind',
     update: (done) ->
       done null, repo?.getAheadBehindCount()

   # Get the number of commits you're ahead of the remote
-  @register 'ahead',
+  register 'ahead',
     update: (done) ->
       git._aheadBehind (err, aheadBehind) ->
         done err, aheadBehind?.ahead

   # Get the number of commits you're behind the remote
-  @register 'behind',
+  register 'behind',
     update: (done) ->
       git._aheadBehind (err, aheadBehind) ->
         done err, aheadBehind?.behind
@@ -84,7 +84,7 @@
   # Returns an array of objects with 'path', 'code', 'staged', 'desc'
   #
   # This command *must* be passed through a formatter before its displayed
-  @register '_status',
+  register '_status',
     update: (done) ->
       return done null, [] unless status = repo?.getStatus()

@@ -104,7 +104,7 @@

   # Get the number of "untracked" files
   # Untracked is defined as new files that are not staged
-  @register 'untracked',
+  register 'untracked',
     update: (done) ->
       git._status (err, statuses) ->
         statuses = _filter_statuses_by_desc(statuses, 'added')
@@ -113,7 +113,7 @@

   # Get the number of modified files
   # Does not matter whether or not they are staged
-  @register 'modified',
+  register 'modified',
     update: (done) ->
       git._status (err, statuses) ->
         count = _filter_statuses_by_desc(statuses, 'modified').length
@@ -121,7 +121,7 @@

   # Get the number of deleted files
   # Does not matter whether or not they are staged
-  @register 'deleted',
+  register 'deleted',
     update: (done) ->
       git._status (err, statuses) ->
         count = _filter_statuses_by_desc(statuses, 'deleted').length
@@ -129,7 +129,7 @@

   # Get the number of "added" files
   # Added is defined as new files that are staged
-  @register 'added',
+  register 'added',
     update: (done) ->
       git._status (err, statuses) ->
         statuses = _filter_statuses_by_desc statuses, 'added'


Updating docs and defaults one more time

--- index.coffee
+++ index.coffee
@@ -10,7 +10,7 @@
   .usage("Usage: #{process.title} --local [local] --remote [remote] --key [key] -p1 [port] -p2 [port]")
   .demand(['local', 'remote', 'key'])
   .default('local', 'redis://127.0.0.1:6379')
-  .default('remote', 'http://127.0.0.1')
+  .default('remote', 'https://reddi.sh')
   .default('remote_port', 8000)
   .default('remote_monitor_port', 8001)
   .default('secure', false)


switch to lower.

switch to lower.

--- index.coffee
+++ index.coffee
@@ -65,6 +65,7 @@
 loadData = (filename, buffer) ->
   draghint.classList.add 'hidden'
   [..., extension] = filename.split '.'
+  extension = extension.toLowerCase()
   rom = null
   if extension is 'zip'
     zip = new JSZip buffer


update the index

update the index

--- index.coffee
+++ index.coffee
@@ -41,7 +41,7 @@
 # refer to the `collectModules` method implementation for more
 # information on how this is being done. See the modules in the
 # framework library to see the structure of the built hieararchy.
-module.exports = @collectPackages __dirname
+do -> module.exports = @collectPackages __dirname
 module.exports.collectPackages = @collectPackages
 module.exports.collectModules = @collectModules
 module.exports.cachedKernel = @cachedKernel


Check for ASAR file directly because we might have an app folder too

--- index.coffee
+++ index.coffee
@@ -33,11 +33,13 @@

     {certificateFile, certificatePassword, remoteReleases, signWithParams} = config

-    appResourcesDirectory = path.join(appDirectory, 'resources', 'app')
-    if grunt.file.isDir(appResourcesDirectory)
+    asarFile = path.join(appDirectory, 'resources', 'app.asar')
+    if !fs.existsSync(asarFile)
+      appResourcesDirectory = path.join(appDirectory, 'resources', 'app')
       appMetadata = grunt.file.readJSON(path.join(appResourcesDirectory, 'package.json'))
     else
-      appMetadata = JSON.parse(asar.extractFile(path.join(appDirectory, 'resources', 'app.asar'), 'package.json'))
+      appMetadata = JSON.parse(asar.extractFile(asarFile, 'package.json'))
+
     metadata = _.extend({}, appMetadata, config)

     metadata.authors ?= metadata.author?.name ? metadata.author ? ''


gtfo camelCase

gtfo camelCase

--- index.coffee
+++ index.coffee
@@ -36,10 +36,10 @@

   normalize_uri options
   https_pattern = /^https:/
-  requestModule = if https_pattern.test options.uri then https else http
+  request_module = if https_pattern.test options.uri then https else http

   _(options).defaults
-    port: if requestModule is http then 80 else 443
+    port: if request_module is http then 80 else 443
     headers: {}

   parsed_uri = url.parse options.uri
@@ -50,7 +50,7 @@
     options.body = new Buffer options.body
     options.headers['content-length'] = options.body.length

-  req = requestModule.request options, (resp) ->
+  req = request_module.request options, (resp) ->
     resp.setEncoding 'utf-8'
     resp.on 'data', (body) ->
       body = JSON.parse body if options.json


Fix issue in preceding being undefined

Fix issue in preceding being undefined

--- index.coffee
+++ index.coffee
@@ -59,6 +59,8 @@
       return indent if precedingRow < 0

       precedingLine = @buffer.lineForRow(precedingRow)
+
+      return indent if not precedingLine?

       indent += 1 if tagStartRegex.testSync(precedingLine) and complexAttributeRegex.testSync(precedingLine)
       indent -= 1 if precedingLine and decreaseNextLineIndentRegex.testSync(precedingLine)


[enh] Add watchers + bugfix

[enh] Add watchers + bugfix

--- index.coffee
+++ index.coffee
@@ -10,6 +10,8 @@
 process = require 'process'
 uuid = require 'node-uuid'
 async = require 'async'
+chokidar = require 'chokidar'
+urlParser = require 'url'
 log = require('printit')
     prefix: 'Data Proxy'

@@ -65,14 +67,13 @@
                 log.info 'Current device properly removed from remote cozy.'

-runReplication = (devicename) ->
+replicateFromRemote = (devicename) ->
     remoteConfig = config.config.remotes[devicename]

     options =
         filter: (doc) ->
             doc.docType is 'Folder' or doc.docType is 'File'

-    urlParser = require 'url'
     url = urlParser.parse remoteConfig.url
     url.auth = devicename + ':' + remoteConfig.devicePassword
     replication = db.replicate.from(urlParser.format(url) + 'cozy', options)
@@ -80,6 +81,91 @@
           console.log info
       .on 'complete', (info) ->
           log.info 'Replication is complete'
+      .on 'error', (err) ->
+          log.error err
+
+
+replicateToRemote = (devicename) ->
+    remoteConfig = config.config.remotes[devicename]
+
+    options =
+        filter: (doc) ->
+            doc.docType is 'Folder' or doc.docType is 'File'
+
+    url = urlParser.parse remoteConfig.url
+    url.auth = "#{devicename}:#{remoteConfig.devicePassword}"
+    replication = db.replicate.to(urlParser.format(url) + 'cozy', options)
+      .on 'change', (info) ->
+          console.log info
+      .on 'complete', (info) ->
+          log.info 'Replication is complete'
+      .on 'error', (err) ->
+          log.error err
+
+
+watchLocalChanges = (devicename) ->
+    # Get config
+    remoteConfig = config.config.remotes[devicename]
+
+    watcher = chokidar.watch remoteConfig.path,
+        ignored: /[\/\\]\./
+        persistent: true
+
+    watcher
+    .on 'add', (path) ->
+        log.info "File added: #{path}"
+        putFile devicename, path, () ->
+    .on 'addDir', (path) ->
+        if path isnt remoteConfig.path
+            log.info "Directory added: #{path}"
+            putDirectory devicename, path, { recursive: true }, () ->
+    .on 'change', (path) ->
+        log.info "File changed: #{path}"
+        putFile devicename, path, () ->
+    .on 'error', (err) ->
+        log.error 'An error occured when watching changes'
+        console.log err
+
+    options =
+        filter: (doc) ->
+            doc.docType is 'Folder' or doc.docType is 'File'
+        live: true
+
+    url = urlParser.parse remoteConfig.url
+    url.auth = devicename + ':' + remoteConfig.devicePassword
+    replication = db.sync(config.dbPath, urlParser.format(url) + 'cozy', options)
+      .on 'change', (info) ->
+          console.log info
+      .on 'uptodate', (info) ->
+          log.info 'Replication is complete, applying changes on the filesystem...'
+          if args.binary?
+              fetchBinaries devicename, {}, () ->
+          else
+              buildFsTree devicename, {}, () ->
+      .on 'error', (err) ->
+          log.error err
+
+
+watchRemoteChanges = (devicename, args) ->
+    # Get config
+    remoteConfig = config.config.remotes[devicename]
+
+    options =
+        filter: (doc) ->
+            doc.docType is 'Folder' or doc.docType is 'File'
+        live: true
+
+    url = urlParser.parse remoteConfig.url
+    url.auth = devicename + ':' + remoteConfig.devicePassword
+    replication = db.replicate.from(urlParser.format(url) + 'cozy', options)
+      .on 'change', (info) ->
+          console.log info
+      .on 'uptodate', (info) ->
+          log.info 'Replication is complete, applying changes on the filesystem...'
+          if args.binary?
+              fetchBinaries devicename, {}, () ->
+          else
+              buildFsTree devicename, {}, () ->
       .on 'error', (err) ->
           log.error err

@@ -124,8 +210,9 @@
                     mkdirp path.join(remoteConfig.path, doc.path), () ->
                         # Find if binary is already downloaded
                         db.get doc.binary.file.id, (err, binaryDoc) ->
-                            if binaryDoc?
+                            if binaryDoc? and binaryDoc.path isnt name
                                 # And move it to the right path
+                                log.info "Moving #{binaryDoc.path} to #{name}"
                                 fs.rename binaryDoc.path, name, () ->
                                     binaryDoc.path = name
                                     db.put binaryDoc, (err, res) ->
@@ -175,7 +262,7 @@
                         path: binaryPath
                     db.put binaryDoc, doc.binary.file.id, doc.binary.file.rev, (err, res) ->
                         if err? and err.status is 409
-                            log.info "Binary already downloaded: #{path.join doc.path, doc.name}"
+                            # Binary already downloaded, ignore
                             callback()
                         else
                             # Fetch binary via CouchDB API
@@ -190,7 +277,7 @@
             , callback

-putDirectory = (devicename, directoryPath, recursive, callback) ->
+putDirectory = (devicename, directoryPath, args, callback) ->
     # Fix callback
     if not callback? or typeof callback is 'object'
         callback = (err) ->
@@ -209,7 +296,7 @@
     absolutePath = path.resolve directoryPath
     relativePath = absolutePath.replace remoteConfig.path, ''
     if relativePath is absolutePath
-        log.error "Directory is not located on the synchronized directory: #{dirPath}"
+        log.error "Directory is not located on the synchronized directory: #{absolutePath}"
         return callback()
     if relativePath.split('/').length > 2
         dirPath = relativePath.replace "/#{dirName}", ''
@@ -225,10 +312,10 @@
         for doc in res.rows
             doc = doc.value
             if doc.path is dirPath and doc.name is dirName
-                log.info "Directory already exists: #{doc.path}/#{doc.name}"
-                if recursive
+                if args.recursive?
                     return putSubFiles callback
                 else
+                    log.info "Directory already exists: #{doc.path}/#{doc.name}"
                     return callback err, res

         log.info "Creating directory doc: #{dirPath}/#{dirName}"
@@ -242,10 +329,10 @@
             tags: []

         db.put document, newId, (err, res) ->
-            if recursive
-                putSubFiles callback
+            if args.recursive?
+                return putSubFiles callback
             else
-                callback err, res
+                return callback err, res

     putSubFiles = (callback) ->
         # List files in directory
@@ -254,13 +341,13 @@
                 fileName = "#{absolutePath}/#{file}"
                 # Upload file if it is a file
                 if fs.lstatSync(fileName).isFile()
-                    putFile devicename, fileName, callback
+                    return putFile devicename, fileName, callback
                 # Upload directory recursively if it is a directory
                 else if fs.lstatSync(fileName).isDirectory()
-                    putDirectory devicename, fileName, recursive, callback
+                    return putDirectory devicename, fileName, { recursive: true }, callback
             if res.length is 0
                 log.info "No file to upload in: #{relativePath}"
-                callback err, res
+                return callback err, res

 putFile = (devicename, filePath, callback) ->
@@ -300,7 +387,7 @@
     fileSize = stats.size

     # Ensure that directory exists
-    putDirectory devicename, ".#{filePath}", false, (err, res) ->
+    putDirectory devicename, path.join(remoteConfig.path, filePath), false, (err, res) ->

         # Fetch only files with the same path/filename
         db.query { map: (doc) -> emit doc._id, doc if doc.docType is 'File' }, (err, res) ->
@@ -381,27 +468,6 @@
                         return callback()

-watchChanges = (devicename) ->
-    remoteConfig = config.config.remotes[devicename]
-
-
-runSync = (devicename) ->
-    remoteConfig = config.config.remotes[devicename]
-
-    options =
-        filter: (doc) ->
-            doc.docType is 'Folder' or doc.docType is 'File'
-
-    urlParser = require 'url'
-    url = urlParser.parse remoteConfig.url
-    url.auth = "#{devicename}:#{remoteConfig.devicePassword}"
-    replication = db.replicate.to(urlParser.format(url) + 'cozy', options)
-      .on 'change', (info) ->
-          console.log info
-      .on 'complete', (info) ->
-          log.info 'Replication is complete'
-      .on 'error', (err) ->
-          log.error err

 displayConfig = ->
@@ -419,9 +485,14 @@
     .action removeRemote

 program
-    .command('replicate <devicename>')
-    .description('Replicate DB documents')
-    .action runReplication
+    .command('replicate-from-remote <devicename>')
+    .description('Replicate remote files/folders to local DB')
+    .action replicateFromRemote
+
+program
+    .command('replicate-to-remote <devicename>')
+    .description('Replicate local files/folders to remote DB')
+    .action replicateToRemote

 program
     .command('build-tree <devicename>')
@@ -447,14 +518,15 @@
     .action putDirectory

 program
-    .command('sync <devicename>')
-    .description('Synchronize binaries')
-    .action runSync
-
-program
-    .command('watch <devicename>')
-    .description('Watch changes on FS')
-    .action watchChanges
+    .command('watch-local <devicename>')
+    .description('Watch changes on the FS')
+    .action watchLocalChanges
+
+program
+    .command('watch-remote <devicename>')
+    .description('Watch changes on the remote DB')
+    .option('-b, --binary', 'automatically fetch binaries')
+    .action watchRemoteChanges

 program
     .command('display-config')


Issue 8 - deprecated WorkspaceView.getActivePaneItem

--- index.coffee
+++ index.coffee
@@ -30,8 +30,8 @@
 module.exports =
   activate: ->
     atom.workspaceView.command 'pretty-json:prettify', '.editor', ->
-      editor = atom.workspaceView.getActivePaneItem()
+      editor = atom.workspace.getActivePaneItem()
       prettify(editor)
     atom.workspaceView.command 'pretty-json:sort-and-prettify', '.editor', ->
-      editor = atom.workspaceView.getActivePaneItem()
+      editor = atom.workspace.getActivePaneItem()
       prettify(editor, true)


refuse to add a duplicate edge

refuse to add a duplicate edge

--- src/lib/Graph.coffee
+++ src/lib/Graph.coffee
@@ -203,6 +203,9 @@
   #
   # Adding an edge will emit the `addEdge` event.
   addEdge: (outNode, outPort, inNode, inPort, metadata) ->
+    for edge in @edges
+      # don't add a duplicate edge
+      return if (edge.from.node is outNode and edge.from.port is outPort and edge.to.node is inNode and edge.to.port is inPort)
     return unless @getNode outNode
     return unless @getNode inNode
     metadata = {} unless metadata


to be straightforward

to be straightforward

--- lib/register-manager.coffee
+++ lib/register-manager.coffee
@@ -18,6 +18,7 @@
 #  [x] 9. The black hole register "_
 #  [ ] 10. Last search pattern register "/

+module.exports =
 class RegisterManager
   constructor: (@vimState) ->
     {@editor, @editorElement, @globalState} = @vimState
@@ -27,7 +28,7 @@

   reset: ->
     @name = null
-    @vimState.toggleClassList('with-register', @name?)
+    @editorElement.classList.toggle('with-register', false)

   destroy: ->
     @subscriptionBySelection.forEach (disposable) ->
@@ -128,7 +129,7 @@

   setName: (@name) ->
     if @name?
-      @vimState.toggleClassList('with-register', true)
+      @editorElement.classList.toggle('with-register', true)
       @vimState.hover.set('"' + @name)
     else
       @vimState.hover.set('"')
@@ -144,5 +145,3 @@
       'linewise'
     else
       'characterwise'
-
-module.exports = RegisterManager


solve merge conflict

solve merge conflict

--- app/assets/javascripts/patients.coffee
+++ app/assets/javascripts/patients.coffee
@@ -48,7 +48,8 @@
         el.prop 'checked', false

-ready = ->
+
+$(document).on 'turbolinks:load', ->
   $(document).on "click", "#toggle-call-log", ->
     $(".old-calls").toggleClass("hidden")
     html = if $(".old-calls").hasClass("hidden") then "View all calls" else "Limit list"
@@ -93,5 +94,3 @@
       placement: 'top',
       title: $(@).data( 'tooltip-text' )
     } )
-
-$(document).on 'ready page:load', ready


Remove `Flash` from todos when finished

Remove `Flash` from todos when finished

--- app/assets/javascripts/todos.js.coffee
+++ app/assets/javascripts/todos.js.coffee
@@ -26,7 +26,6 @@
         return

       success: (data, textStatus, jqXHR) ->
-        new Flash(data.notice, 'success')
         _this.clearDone($this.closest('li'))
         return


Clarify some todo code and fix a bug when canceling editing a todo

--- app/assets/javascripts/todos.js.coffee
+++ app/assets/javascripts/todos.js.coffee
@@ -37,11 +37,11 @@

 $(window).load ->
-  $("form.todo").live "ajax:beforeSend", (event,xhr,status) ->
+  $("form.todo-new").live "ajax:beforeSend", (event,xhr,status) ->
     OUT.todos.createFakeTodo(this)
     $(event.target).find('input[type="text"]').val('').select()

-  $(".todo-checkbox input").live "change", (event) ->
+  $("div.todo-checkbox input").live "change", (event) ->
     $(event.target).parents(".content-item").addClass("pending")
     checked = $(event.target).is(':checked')
     data = {"active": !checked}


Filled out additional missing types for testing expression equality

--- javascripts/lib/math/expression_equality.coffee
+++ javascripts/lib/math/expression_equality.coffee
@@ -25,6 +25,35 @@
       logger.instrument(name: "buildIsEqual function", fn: isEqualFunction)

+    # Addition
+    ref.forType(comps.addition, {
+      isEqual: (buildIsEqual(comps.addition))
+      })
+
+
+    # Blank
+    ref.forType(comps.blank, {
+      isEqual: (buildIsEqual(comps.blank))
+      })
+
+
+    # Division
+    ref.forType(comps.division, {
+      isEqual: (buildIsEqual(comps.division))
+      })
+
+
+
+    # Exponentiation
+    ref.forType(comps.exponentiation, {
+      isEqual: (buildIsEqual(comps.exponentiation)),
+      _simpleIsEqual: (other)->
+        ref.refine(@base()).isEqual(other.base()) and
+          ref.refine(@power()).isEqual(other.power())
+      })
+
+
+    # Expression
     ref.forType(comps.expression, {
       isExpressionEqual: (other)->
         logger.info("isExpressionEqual", @unrefined(), other)
@@ -45,6 +74,15 @@
           @isExpressionEqual(other)
     })

+
+
+    # Multiplication
+    ref.forType(comps.multiplication, {
+      isEqual: (buildIsEqual(comps.multiplication))
+      })
+
+
+    # Number
     ref.forType(comps.number, {
       isEqual: (buildIsEqual(comps.number)),
       _simpleIsEqual: (other)->
@@ -79,7 +117,15 @@
     ref.forDefault({
       isEqual: -> console.log(@); throw "NOT IMPLEMENTED"
     })
+    # Number
+    ref.forType(comps.pi, {
+      isEqual: (buildIsEqual(comps.pi))
+    })

+    # Subtraction
+    ref.forType(comps.subtraction, {
+      isEqual: (buildIsEqual(comps.subtraction))
+    })

     class ExpressionEquality
       initialize: (@first)->


social: add an option for disabling websockets

--- workers/social/lib/social/models/kitestack.coffee
+++ workers/social/lib/social/models/kitestack.coffee
@@ -5,13 +5,16 @@
   @share()

   @set
-    sharedMethods :
-      static      :
-        fetchInfo : (signature Function)
-        setInfo   : (signature Object, Function)
-    schema        :
-      ratio       : Number
-      isEnabled   : Boolean
+    sharedMethods   :
+      static        :
+        fetchInfo   : (signature Function)
+        setInfo     : (signature Object, Function)
+    schema          :
+      ratio         : Number
+      isEnabled     : Boolean
+      useWebSockets :
+        type        : Boolean
+        default     : true

   @fetchInfo = (callback) ->
     @one {}, (err, info) ->
@@ -26,14 +29,15 @@

         callback null, info

-  @setInfo = secure (client, { ratio, isEnabled }, callback) ->
+  @setInfo = secure (client, { ratio, isEnabled, useWebSockets }, callback) ->
     { connection:{ delegate }} = client

     if delegate.can 'flag'
       modifier = $set: {}

-      modifier.$set.ratio       = ratio       if ratio?
-      modifier.$set.isEnabled   = isEnabled   if isEnabled?
+      modifier.$set.ratio         = ratio           if ratio?
+      modifier.$set.isEnabled     = isEnabled       if isEnabled?
+      modifier.$set.useWebSockets = useWebSockets   if useWebSockets?

       @getCollection().update {}, modifier, upsert: yes, (err) -> callback err
     else


Default array not needed

Default array not needed

--- resources/assets/coffee/react/_components/flag-country.coffee
+++ resources/assets/coffee/react/_components/flag-country.coffee
@@ -21,7 +21,7 @@

 bn = 'flag-country'

-@FlagCountry = ({country, modifiers = []}) ->
+@FlagCountry = ({country, modifiers}) ->
   return null if !country.code?

   blockClass = osu.classWithModifiers(bn, modifiers)


added newlines; moved help()

added newlines; moved help()

--- code/li.coffee
+++ code/li.coffee
@@ -1,4 +1,7 @@
 #!/usr/bin/env coffee
+
+help = (args) ->
+  process.stdout.write('Very helpful help\n')

 exports.main = (args) ->
     # If supplied *args* should be a list of arguments,
@@ -8,9 +11,6 @@
         args = process.argv[1..]
     switch args[1]
       when 'help' then help(args)
-      else process.stderr.write('Try li help for help')
+      else process.stderr.write('Try li help for help\n')

 exports.main()
-
-help = (args) ->
-  process.stdout.write('Very helpful help')


added initial version of utils.coffee

added initial version of utils.coffee

--- src/lib/utils.coffee
+++ src/lib/utils.coffee
@@ -0,0 +1,140 @@
+# [Template](http://neocotic.com/template)
+# (c) 2012 Alasdair Mercer
+# Freely distributable under the MIT license.
+# For all details and documentation:
+# <http://neocotic.com/template>
+
+#### Private variables
+
+# Mapping for internationalization handlers.
+# Each handler represents an attribute (based on the property name) and is
+# called for each attribute found in the current `document`.
+i18nHandlers   =
+
+  # Replace the HTML content of `element` with the message looked up for
+  # `value`.
+  'i18n-content': (element, value, subMap) ->
+    subs = i18nSubs element, value, subMap
+    element.innerHTML = utils.i18n value, subs
+
+  # Replace the value of the properties and/or attributes of `element` with the
+  # messages looked up for their corresponding values.
+  'i18n-values':  (element, value, subMap) ->
+    subs  = i18nSubs element, value, subMap
+    parts = value.replace(/\s/g, '').split ';'
+    for part in parts
+      prop = part.match /^([^:]+):(.+)$/
+      if prop
+        propName = prop[1]
+        propExpr = prop[2]
+        if propName.indexOf('.') is 0
+          path = propName.slice(1).split '.'
+          obj = element
+          obj = obj[path.shift()] while obj and path.length > 1
+          if obj
+            obj[path] = utils.i18n value, subs
+            i18nProcess element, subMap if path is 'innerHTML'
+        else
+          element.setAttribute propName, utils.i18n propExpr, subs
+# List of internationalization attributes/handlers available.
+i18nAttributes = []
+i18nAttributes.push key for key of i18nHandlers
+# Selector containing the available internationalization attributes/handlers
+# which is used by `i18nProcess` to query all elements.
+i18nSelector   = "[#{i18nAttributes.join '],['}]"
+
+#### Private functions
+
+# Find all elements to be internationalized and call their corresponding
+# handler(s).
+i18nProcess = (node, subMap) ->
+  for element in node.querySelectorAll i18nSelector
+    for name in i18nAttributes
+      attribute = element.getAttribute name
+      i18nHandlers[name] element, attribute, subMap if attribute?
+
+# Find an array of substitution strings using the element's ID and the message
+# key as the mapping.
+i18nSubs = (element, value, subMap) ->
+  if subMap
+    for prop of subMap when subMap.hasOwnProperty(prop) and prop is element.id
+      for subProp of subMap[prop] when subMap[prop].hasOwnProperty subProp
+        if subProp is value
+          subs = subMap[prop][subProp]
+          break
+      break
+  return subs
+
+#### Utilities setup
+
+utils = window.utils =
+
+  #### Data functions
+
+  # Determine whether or not the specified key exists in `localStorage`.
+  exists: (key) ->
+    return localStorage.hasOwnProperty key
+
+  # Retrieve the value associated with the specified key from `localStorage`.
+  # If the value is found, parse it as JSON before being returning it; otherwise
+  # return `undefined`.
+  get: (key) ->
+    value = localStorage[key]
+    return if value? then JSON.parse value else value
+
+  # Initialize the value of the specified key in `localStorage`.
+  # If the value is currently `undefined`, assign the specified default value;
+  # otherwise reassign itself.
+  init: (key, defaultValue) ->
+    value = utils.get key
+    return utils.set key, value ? defaultValue
+
+  # Remove the specified key from `localStorage`.
+  remove: (key) ->
+    exists = utils.exists key
+    delete localStorage[key]
+    return exists
+
+  # Copy the value of the existing key to that of the new key then remove the
+  # old key from `localStorage`.
+  # If the old key doesn't exist in `localStorage`, assign the specified default
+  # value to it instead.
+  rename: (oldKey, newKey, defaultValue) ->
+    if utils.exists oldKey
+      utils.init newKey, utils.get oldKey
+      utils.remove oldKey
+    else
+      utils.init newKey, defaultValue
+
+  # Set the value of the specified key in `localStorage`.
+  # If the specified value is `undefined`, assign that value directly to the
+  # key; otherwise transform it to a JSON string beforehand.
+  set: (key, value) ->
+    oldValue = utils.get key
+    localStorage[key] = if value? then JSON.stringify value else value
+    return oldValue
+
+  #### Internationalization functions
+
+  # Convenient shorthand for `chrome.i18n.getMessage`.
+  i18n: ->
+    chrome.i18n.getMessage.apply chrome.i18n, arguments
+
+  # Internationalize the specified attribute of all the selected elements.
+  i18nAttribute: (selector, attribute, value, subs) ->
+    elements = document.querySelectorAll selector
+    # Ensure the substitution string(s) are in an array.
+    subs = [subs] if typeof subs is 'string'
+    for element in elements
+      element.setAttribute attribute, utils.i18n value, subs
+
+  # Internationalize the contents of all the selected elements.
+  i18nContent: (selector, value, subs) ->
+    elements = document.querySelectorAll selector
+    # Ensure the substitution string(s) are in an array.
+    subs = [subs] if typeof subs is 'string'
+    element.innerHTML = utils.i18n value, subs for element in elements
+
+  # Perform all internationalization setup required for the current page.
+  i18nSetup: (subMap) ->
+    i18nProcess document, subMap


Disabling an error that occurs on the dev server with the partial db.

--- server/users/User.coffee
+++ server/users/User.coffee
@@ -130,7 +130,7 @@
   @findById id, (err, user) ->
     log.error err if err?
     err = new Error "Could't find user with id '#{id}'" unless user or err
-    return done err if err?
+    return done() if err?
     user.incrementStat statName, done, inc=1

 UserSchema.methods.incrementStat = (statName, done, inc=1) ->


Use pjs directory for PaperScript

Use pjs directory for PaperScript

--- config/application.coffee
+++ config/application.coffee
@@ -13,13 +13,13 @@
   loadNpmTasks: ['grunt-contrib-copy']

   copy:
-    paper:
+    paperscript:
       files: [
-        { expand: true, cwd: 'app', src: [ 'paper/**' ], dest: 'generated/' },
-        { expand: true, cwd: 'app', src: [ 'paper/**' ], dest: 'dist/' }
+        { expand: true, cwd: 'app', src: [ 'pjs/**' ], dest: 'generated/' },
+        { expand: true, cwd: 'app', src: [ 'pjs/**' ], dest: 'dist/' }
       ]

   watch:
-    paper:
-      files: ['app/paper/**']
-      tasks: ['copy:paper']
+    paperscript:
+      files: ['app/pjs/**']
+      tasks: ['copy:paperscript']


check incoming hash when present

check incoming hash when present

--- app/coffee/ShareJsUpdateManager.coffee
+++ app/coffee/ShareJsUpdateManager.coffee
@@ -6,6 +6,7 @@
 {EventEmitter} = require "events"
 util = require "util"
 RealTimeRedisManager = require "./RealTimeRedisManager"
+crypto = require "crypto"

 ShareJsModel:: = {}
 util.inherits ShareJsModel, EventEmitter
@@ -42,6 +43,10 @@
 			logger.log project_id: project_id, doc_id: doc_id, error: error, "applied update"
 			model.getSnapshot doc_key, (error, data) =>
 				return callback(error) if error?
+				# only check hash when present and no other updates have been applied
+				if update.hash? and update.v == version
+					ourHash = ShareJsUpdateManager._computeHash(data.snapshot)
+					return callback(new Error("Invalid hash")) if ourHash != update.hash
 				docLines = data.snapshot.split(/\r\n|\n|\r/)
 				callback(null, docLines, data.v, model.db.appliedOps[doc_key] or [])

@@ -53,3 +58,9 @@
 	_sendOp: (project_id, doc_id, op) ->
 		RealTimeRedisManager.sendData {project_id, doc_id, op}

+	_computeHash: (content) ->
+		return crypto.createHash('sha1')
+			.update("blob " + content.length + "\x00")
+			.update(content, 'utf8')
+			.digest('hex')
+


Fixed bug with missing callback argument.

--- src/Objects.coffee
+++ src/Objects.coffee
@@ -64,17 +64,13 @@
     _.each obj, (value, key) ->
       if Types.isObject(value)
         branches.push(key)
-    if branches.length == 0
-      callback(obj)
-    else
-      _.each branches, (branch) =>
-        @traverseLeaves(obj[branch])
+    if branches.length == 0 then callback(obj)
+    else _.each branches, (branch) => @traverseLeaves obj[branch], callback

   traverseValues: (obj, callback) ->
     _.each obj, (value, key) =>
       callback(value, key)
-      if Types.isObject(value)
-        @traverseValues(value, callback)
+      if Types.isObject(value) then @traverseValues value, callback

   inverse: (obj) ->
     result = {}


counters, now for real :-) (as in, they can go on components)

--- ui.coffee
+++ ui.coffee
@@ -1,7 +1,4 @@
 ui = CBGA.ui = {}
-
-getNextController = (context) ->
-

 class ui.Panel
   constructor: ({@name, @title, @owner, @visibility, @icon, @contains, @provides,
@@ -25,6 +22,10 @@

 # Slots are really later on in the backlog, but here's the API anyway
+# Slots are for cases where exactly zero or one component of certain
+# characteristics can be played. For example, in Girls War, the current
+# trends have one slot for the positive trend, and one for the negative.
+# The units *may* be implemented with a slot for the manager.
 class ui.Slot
   constructor: ({@id, @title, @panel} = {}) ->
     check @id, String
@@ -48,8 +49,8 @@

 class ui.ComponentType
   constructor: ({@name, @width, @height, @isCounter, @stackProperty, @stackAfter,
-                 @displayNameSingular, @displayNamePlural,
-                 @draggable, @template, @summaryTemplate} = {}) ->
+                 @displayNameSingular, @displayNamePlural, @controllerClass,
+                 @draggable, @contains, @template, @summaryTemplate} = {}) ->
     check @name, String
     check @width, Match.Optional Number
     check @height, Match.Optional Number
@@ -66,10 +67,14 @@
     check @displayNameSingular, String
     @displayNamePlural ?= @displayNameSingular
     check @displayNamePlural, String
+    @controllerClass ?= ui.ComponentController
+    check @controllerClass, CBGA.Match.ClassOrSubclass ui.Controller
+    @draggable ?= false
+    check @draggable, Boolean
+    @contains ?= []
+    check @contains, [String]
     check @template, Match.Optional String
     check @summaryTemplate, Match.Optional String
-    @draggable ?= false
-    check @draggable, Boolean

   render: (component) ->
     new Blaze.Template =>
@@ -79,8 +84,8 @@
       else
         rules = component.game().rules
         Template["#{rules.replace /\s/g, ''}ComponentView"] ? Template.componentDefaultView
-      Blaze.With (type: @, component: component), ->
-        Blaze.With component, -> template
+      controller = new @controllerClass uiType: @, component: component
+      Blaze.With (controller: controller, component: component), -> template

   summary: (count, stack) ->
     if stack?
@@ -151,7 +156,52 @@

 class ui.Controller extends EventEmitter
-  # does nothing for now, but can be used for instanceof
+  handleDragLeave: (event) ->
+    if ((event.relatedTarget is null or
+        not event.currentTarget.contains event.relatedTarget) and
+        event.target is event.currentTarget)
+      $(event.currentTarget).removeClass 'drag-allowed'
+
+  # XXX not sure at all about this one
+  getContainer: (owner) ->
+    owner
+
+  # Override this to set other properties on move; but ideally don't, if
+  # possible, that should be done in the container class instead
+  doMoveComponent: (component, owner, oldContainer, count = 1) ->
+    if component instanceof CBGA.Component
+      if count is 1
+        component.moveTo @getContainer owner
+      else
+        type = @rules.getComponentType component.type
+        selector = type: component.type
+        if type.stackProperty?
+          selector[type.stackProperty] = component[type.stackProperty]
+        component.container().find selector, limit: count
+        .forEach (eachComponent) =>
+          eachComponent.moveTo @getContainer owner
+
+    else if component instanceof ui.ComponentStack
+      type = @rules.getComponentType component.type
+      selector = type: component.type
+      if type.stackProperty?
+        selector[type.stackProperty] = component[type.stackProperty]
+      component.container().find selector, limit: count
+      .forEach (eachComponent) =>
+        eachComponent.moveTo @getContainer owner
+
+    else if component.provider
+      # This can't be done on the client because typically a provider is private
+      # (deck of cards, for example)
+      # XXX missing stackProperty
+      target = @getContainer owner
+      options =
+        rules: @rules.name
+        type: component.type
+        source: component.container._id
+        target: target._id
+        count: count
+      Meteor.call 'component.draw', options

 class ui.PanelContainerController extends ui.Controller
@@ -185,8 +235,8 @@
     if @panel.contains.length
       new Blaze.Template =>
         owner ?= Template.currentData().owner
-        # XXX: Not sure why I used _.map instead of coffee's for.
-        # It does have the side effect of skipping undefineds (counters).
+        # Why _.map instead of coffee's for?
+        # Because then it has its own locals.
         _.map @panel.contains, (typeName) =>
           typeId = typeName.replace /\s/g, '-'
           type = @rules.getComponentType typeName
@@ -273,12 +323,6 @@
       event.preventDefault()
       true

-  handleDragLeave: (event) ->
-    if ((event.relatedTarget is null or
-        not event.currentTarget.contains event.relatedTarget) and
-        event.target is event.currentTarget)
-      $(event.currentTarget).removeClass 'drag-allowed'
-
   handleDrop: (event) ->
     $(event.currentTarget).removeClass 'drag-allowed'
     operation = ui.DragAndDropService.get event
@@ -287,43 +331,45 @@
     owner = @getOwner(event.currentTarget)
     @doMoveComponent operation.component, owner,
       operation.sourceController.getContainer operation.sourceOwner
-
-  # Override this to set other properties on move; but ideally don't, if
-  # possible, that should be done in the container class instead
-  doMoveComponent: (component, owner, oldContainer, count = 1) ->
-    if component instanceof CBGA.Component
-      if count is 1
-        component.moveTo @getContainer owner
-      else
-        type = @rules.getComponentType component.type
-        selector = type: component.type
-        if type.stackProperty?
-          selector[type.stackProperty] = component[type.stackProperty]
-        component.container().find selector, limit: count
-        .forEach (eachComponent) =>
-          eachComponent.moveTo @getContainer owner
-
-    else if component instanceof ui.ComponentStack
-      type = @rules.getComponentType component.type
-      selector = type: component.type
-      if type.stackProperty?
-        selector[type.stackProperty] = component[type.stackProperty]
-      component.container().find selector, limit: count
-      .forEach (eachComponent) =>
-        eachComponent.moveTo @getContainer owner
-
-    else if component.provider
-      # This can't be done on the client because typically a provider is private
-      # (deck of cards, for example)
-      # XXX missing stackProperty
-      target = @getContainer owner
-      options =
-        rules: @rules.name
-        type: component.type
-        source: component.container._id
-        target: target._id
-        count: count
-      Meteor.call 'component.draw', options
+    true
+
+
+# XXX there's an API mismatch since a PanelContainerController is bound to a
+# panel and shared between containers, while a ComponentController is bound to
+# a specific component. Have to evaluate if it's a problem.
+class ui.ComponentController extends ui.Controller
+  constructor: ({@uiType, @component} = {}) ->
+    @rules = CBGA.getGameRules @component.game().rules
+
+  # We'll probably need something like renderFull for recursive-ish components
+  # (e.g. cards that go on cards)
+  renderCounters: ->
+    new Blaze.Template =>
+      if @uiType.contains.length
+        _.map @uiType.contains, (typeName) =>
+          type = @rules.getComponentType typeName
+          if type.isCounter
+            type.renderCounter @component
+
+  handleDragOver: (event) ->
+    operation = ui.DragAndDropService.get event
+    return unless operation
+    return if operation.sourceOwner._id is @component._id and operation.sourceController is @
+    if operation.component.type in @uiType.contains
+      $(event.currentTarget).addClass 'drag-allowed'
+      event.preventDefault()
+      true
+
+  handleDrop: (event) ->
+    $(event.currentTarget).removeClass 'drag-allowed'
+    operation = ui.DragAndDropService.get event
+    return unless operation
+    event.preventDefault()
+    event.stopImmediatePropagation()
+    console.log 'drop', operation
+    @doMoveComponent operation.component, @component,
+      operation.sourceController.getContainer operation.sourceOwner
+    true

 class ui.DragAndDropOperation
@@ -385,7 +431,7 @@

   installHandlers: (template,
                     componentSelector = '.component[draggable], .provider[draggable]',
-                    panelSelector = '.game-panel') ->
+                    panelSelector = '.game-panel, .component.container') ->
     handlers = {}

     makeKey = (event, selector) ->


fix: turn off browser autocomplete for typeaheads (NC-1755)

--- src/controls/TypeAhead/Input.coffee
+++ src/controls/TypeAhead/Input.coffee
@@ -72,6 +72,7 @@
           value={this.itemValue()}
         />
         <input
+          autoComplete="off"
           className="form-control"
           type="search"
           name={this.props.dataKey+"_helper"}


:art: Call updateLineMessages instead of updateCurrentLine Reason: It's name is more expressive

--- lib/editor-linter.coffee
+++ lib/editor-linter.coffee
@@ -9,7 +9,7 @@
     @inProgressFly = false

     if @editor is atom.workspace.getActiveTextEditor()
-      @linter.views.updateCurrentLine @editor.getCursorBufferPosition()?.row
+      @linter.views.updateLineMessages @editor.getCursorBufferPosition()?.row

     @emitter = new Emitter
     @subscriptions = new CompositeDisposable
@@ -19,7 +19,7 @@
     )
     @subscriptions.add(
       @editor.onDidChangeCursorPosition ({newBufferPosition}) =>
-        @linter.views.updateCurrentLine(newBufferPosition.row)
+        @linter.views.updateLineMessages(newBufferPosition.row)
         @linter.views.updateBubble(newBufferPosition)
     )
     @subscriptions.add(