Denorm bugfixes

Denorm bugfixes

--- denorm.coffee
+++ denorm.coffee
@@ -55,7 +55,12 @@
     reactiveKey = "#{reactiveCalcObj.modelName}.#{JSON.stringify reactiveCalcObj.instanceId}.#{reactiveCalcObj.reactiveName}"

     modelClass = J.models[reactiveCalcObj.modelName]
-    instance = modelClass.fetchOne reactiveCalcObj.instanceId
+    projection = {}
+    projection[reactiveCalcObj.reactiveName] = true
+    instance = modelClass.fetchOne(
+        reactiveCalcObj.instanceId
+        fields: projection
+    )

     # While this is recalculating, J._reactiveCalcsInProgress still contains reactiveKey
     # or else a different fiber could redundantly start the same recalc
@@ -106,21 +111,22 @@
         if J._reactiveCalcQueue.length > 0
             console.log "    (#{J._reactiveCalcQueue.length} more in queue)"

+        oldDoc = instance.toDoc()
+        oldDoc._reactives = instance._reactives
+
         watchedQuerySpecSet = null # watchedQsString: true
-        wrappedValue = undefined
+        unwrappedValue = undefined
         J._watchedQuerySpecSet.withValue {}, =>
-            J.assert not instance._watcherReactiveName?
-            instance._watcherReactiveName = reactiveName
-            wrappedValue = J.Var(reactiveSpec.val.call instance).get()
+            unwrappedValue = J.Var.deepUnwrap reactiveSpec.val.call instance
             watchedQuerySpecSet = J._watchedQuerySpecSet.get()
-            delete instance._watcherReactiveName
-
-        if wrappedValue instanceof J.List
-            value = wrappedValue.toArr()
-        else if wrappedValue instanceof J.Dict
-            value = wrappedValue.toObj()
-        else
-            value = wrappedValue
+
+        escapedSubdoc = J.Model._getEscapedSubdoc unwrappedValue
+        if not EJSON.equals escapedSubdoc, oldDoc._reactives[reactiveName]?.val
+            newDoc = _.clone oldDoc
+            newDoc._reactives = _.clone oldDoc._reactives
+            newDoc._reactives[reactiveName] =
+                val: escapedSubdoc
+            J.denorm.resetWatchers instance.modelClass.name, instance._id, oldDoc, newDoc, timestamp

         # console.log "...done recalc <#{instance.modelClass.name} #{JSON.stringify instance._id}>.#{reactiveName}"

@@ -132,7 +138,7 @@

         setter = {}
         setter["_reactives.#{reactiveName}"] =
-            val: J.Model._getEscapedSubdoc value
+            val: escapedSubdoc
             watching: J.Model._getEscapedSubdoc mergedWatchedQuerySpecs
             ts: timestamp
             dirty: false
@@ -141,17 +147,10 @@
             $set: setter
         )

-        wrappedValue
+        J.Var.wrap unwrappedValue

     resetWatchers: (modelName, instanceId, oldDoc, newDoc, timestamp = new Date(), callback) ->
-        console.log "resetWatchers <#{modelName} #{JSON.stringify(instanceId)}>",
-            JSON.stringify
-                oldDoc: _.keys(oldDoc)
-                oldReactives: _.keys(oldDoc._reactives ? {})
-                newDoc: _.keys(newDoc)
-                newReactives: _.keys(newDoc._reactives ? {})
-
         # Total number of running calls to resetWatchersHelper
         semaphore = 0

@@ -233,6 +232,8 @@
                 termMatcher

+            changedFieldSpecs = []
+
             makeWatcherMatcher = (instanceId, mutableOldValues, mutableNewValues) ->
                 # Makes sure every fieldSpec in the watching-selector is consistent
                 # with either oldValues or newValues
@@ -275,15 +276,16 @@
                         projectionKey = "fields.#{J.Model.escapeDot fieldSpec.map(J.Model.escapeDot).join('.')}"
                         sortSpecKey = "sort.#{J.Model.escapeDot fieldSpec.map(J.Model.escapeDot).join('.')}"

-                        #if changed
-                        #    console.log "***changed: #{JSON.stringify fieldSpec}
-                        #        #{JSON.stringify oldValue: oldValue, newValue: newValue}"
-
-                        selectable =
-                            if fieldSpec[0] of modelClass.fieldSpecs
-                                modelClass.fieldSpecs[fieldSpec[0]].selectable ? true
-                            else
-                                modelClass.reactiveSpecs[fieldSpec[0]].selectable ? false
+                        if changed and fieldSpec.length is 1
+                            # Debugging
+                            changedFieldSpecs.push fieldSpec.map(J.Model.escapeDot).join('.')
+
+                        if fieldSpec[0] of modelClass.fieldSpecs
+                            selectable = modelClass.fieldSpecs[fieldSpec[0]].selectable ? true
+                            watchable = modelClass.fieldSpecs[fieldSpec[0]].watchable ? modelClass.watchable
+                        else
+                            selectable = modelClass.reactiveSpecs[fieldSpec[0]].selectable ? false
+                            watchable = modelClass.reactiveSpecs[fieldSpec[0]].watchable ? modelClass.watchable

                         possibleValues = []
                         if oldValue isnt undefined
@@ -305,21 +307,22 @@
                                 term[sortSpecKey] = $exists: true
                                 changedSubfieldSortSpecMatcher.push term

-                            term = {}
-                            term[projectionKey] = true
-                            changedSubfieldAlacarteProjectionMatcher.push term
-
-                            term = {}
-                            term[projectionKey] = true
-                            if fieldSpecPrefix.length is 0
-                                J.assert subfieldName of modelClass.fieldSpecs or subfieldName of modelClass.reactiveSpecs
-                                if subfieldName of modelClass.fieldSpecs
-                                    if modelClass.fieldSpecs[subfieldName].include ? true
-                                        term[projectionKey] = $in: [null, true]
-                                else
-                                    if modelClass.reactiveSpecs[subfieldName].include ? false
-                                        term[projectionKey] = $in: [null, true]
-                            changedSubfieldOverrideProjectionMatcher.push term
+                            if watchable
+                                term = {}
+                                term[projectionKey] = true
+                                changedSubfieldAlacarteProjectionMatcher.push term
+
+                                term = {}
+                                term[projectionKey] = true
+                                if fieldSpecPrefix.length is 0
+                                    J.assert subfieldName of modelClass.fieldSpecs or subfieldName of modelClass.reactiveSpecs
+                                    if subfieldName of modelClass.fieldSpecs
+                                        if modelClass.fieldSpecs[subfieldName].include ? true
+                                            term[projectionKey] = $in: [null, true]
+                                    else
+                                        if modelClass.reactiveSpecs[subfieldName].include ? false
+                                            term[projectionKey] = $in: [null, true]
+                                changedSubfieldOverrideProjectionMatcher.push term

                         if J.util.isPlainObject(oldValue) or J.util.isPlainObject(newValue)
                             addSelectorClauses(
@@ -383,12 +386,10 @@
             mutableOldValues = {}
             mutableNewValues = {}
             for fieldName, fieldSpec of modelClass.fieldSpecs
-                continue if not (fieldSpec.watchable ? modelClass.watchable)
                 mutableOldValues[fieldName] = oldDoc[fieldName]
                 mutableNewValues[fieldName] = newDoc[fieldName]
             for reactiveName, reactiveSpec of modelClass.reactiveSpecs
                 continue if not reactiveSpec.denorm
-                continue if not (reactiveSpec.watchable ? modelClass.watchable)
                 mutableOldValues[reactiveName] = oldDoc._reactives?[reactiveName]?.val
                 mutableNewValues[reactiveName] = newDoc._reactives?[reactiveName]?.val

@@ -396,7 +397,11 @@
                 semaRelease()
                 return null

+            changedFieldSpecs = []
             watcherMatcher = makeWatcherMatcher instanceId, mutableOldValues, mutableNewValues
+
+            console.log "ResetWatchersHelper: <#{modelName} #{JSON.stringify instanceId}> with changes to
+                #{JSON.stringify changedFieldSpecs}"

             watcherMatcherLength = JSON.stringify(watcherMatcher).length
             if watcherMatcherLength > 10000
@@ -405,6 +410,7 @@
                 console.log JSON.stringify watcherMatcher

             resetCountByModelReactive = {} # "#{watcherModelName}.#{reactiveName}": resetCount
+            resetDebugsByModelReactive = {} # "#{watcherModelName}.#{reactiveName}": [debugStrings]
             resetOneWatcherDoc = (watcherModelName, watcherReactiveName) ->
                 lockKey = "#{watcherModelName}.#{watcherReactiveName}"
                 return if lockSema[lockKey]
@@ -424,6 +430,7 @@
                 setter["_reactives.#{watcherReactiveName}.dirty"] = true

                 resetCountByModelReactive["#{watcherModelName}.#{watcherReactiveName}"] ?= 0
+                resetDebugsByModelReactive["#{watcherModelName}.#{watcherReactiveName}"] ?= []
                 watcherModelClass.collection.rawCollection().findAndModify(
                     selector
                 ,
@@ -441,6 +448,7 @@

                         if oldWatcherDoc?
                             resetCountByModelReactive["#{watcherModelName}.#{watcherReactiveName}"] += 1
+                            resetDebugsByModelReactive["#{watcherModelName}.#{watcherReactiveName}"].push oldWatcherDoc._id

                             priority = undefined
                             for dataSessionId, fieldsByModelIdQuery of J._dataSessionFieldsByModelIdQuery
@@ -469,28 +477,19 @@

                             # Also branch into resetting all docs watching this watching-reactive in this doc
                             if priority?
-                                reactiveCalcObj = J._enqueueReactiveCalc watcherModelName, oldWatcherDoc._id, watcherReactiveName, priority
-                                reactiveCalcObj.future.resolve (err, value) ->
-                                    unwrappedValue = J.Model._getEscapedSubdoc(
-                                        if value instanceof J.List
-                                            value.toArr()
-                                        else if value instanceof J.Dict
-                                            value.toObj()
-                                        else
-                                            value
-                                    )
-                                    if not EJSON.equals unwrappedValue, oldWatcherDoc._reactives?[watcherReactiveName]?.val
-                                        newWatcherDoc._reactives[watcherReactiveName] =
-                                            val: unwrappedValue
-                                        resetWatchersHelper watcherModelName, oldWatcherDoc._id, oldWatcherDoc, newWatcherDoc, timestamp
+                                # Recalc will take care of invalidation propagation and call resetWatchers
+                                J._enqueueReactiveCalc watcherModelName, oldWatcherDoc._id, watcherReactiveName, priority
                             else
+                                # Aggressively propagate the reset so that other recalcs won't use the potentially-about-
+                                # to-change value, even though the value might not actually change once it recalculates
                                 resetWatchersHelper watcherModelName, oldWatcherDoc._id, oldWatcherDoc, newWatcherDoc, timestamp

                         else
                             numWatchersReset = resetCountByModelReactive["#{watcherModelName}.#{watcherReactiveName}"]
+                            resetDebugs = resetDebugsByModelReactive["#{watcherModelName}.#{watcherReactiveName}"]
                             if numWatchersReset
                                 console.log "    <#{watcherModelName}>.#{watcherReactiveName}:
-                                    #{numWatchersReset} watchers reset by saving <#{modelName} #{JSON.stringify instanceId}>"
+                                    #{numWatchersReset} watchers reset by saving <#{modelName} #{JSON.stringify instanceId}>: #{JSON.stringify resetDebugs}"
                                 # console.log "selector: #{JSON.stringify selector, null, 4}"

                             lockRelease watcherModelName, watcherReactiveName


removing pagination from projects

removing pagination from projects

[5cd2a37e04b3c888fbe2d4ff945b9f21e230c6d1]

--- review_front/app/routes/projects/index.coffee
+++ review_front/app/routes/projects/index.coffee
@@ -1,8 +1,7 @@
 `import Ember from 'ember'`
-`import RouteMixin from 'ember-cli-pagination/remote/route-mixin'`

-ProjectsIndex = Ember.Route.extend RouteMixin,
-  model: (params) ->
+ProjectsIndex = Ember.Route.extend
+  model: ->
     @modelFor('projects')

 `export default ProjectsIndex`


simplifying some string interpolation in Range#compileNode

--- nodes.coffee
+++ nodes.coffee
@@ -651,16 +651,17 @@
     varPart  = "#{idx} = #{@from}"
     varPart += ", #{@to}" if @to isnt @toVar
     varPart += ", #{@step}" if @step isnt @stepVar
+    [lt, gt] = ["#{idx} <#{@equals}", "#{idx} >#{@equals}"]

     # Generate the condition.
     condPart = if @stepNum
-      condPart = if +@stepNum > 0 then "#{idx} <#{@equals} #{@toVar}" else "#{idx} >#{@equals} #{@toVar}"
+      condPart = if +@stepNum > 0 then "#{lt} #{@toVar}" else "#{gt} #{@toVar}"
     else if known
       [from, to] = [+@fromNum, +@toNum]
-      condPart   = if from <= to then "#{idx} <#{@equals} #{to}" else "#{idx} >#{@equals} #{to}"
+      condPart   = if from <= to then "#{lt} #{to}" else "#{gt} #{to}"
     else
       cond     = "#{@fromVar} <= #{@toVar}"
-      condPart = "#{cond} ? #{idx} <#{@equals} #{@toVar} : #{idx} >#{@equals} #{@toVar}"
+      condPart = "#{cond} ? #{lt} #{@toVar} : #{gt} #{@toVar}"

     # Generate the step.
     stepPart = if @stepVar


tweaks to Slice#compileNode

tweaks to Slice#compileNode

--- nodes.coffee
+++ nodes.coffee
@@ -664,24 +664,25 @@
   constructor: (@range) ->
     super()

-  compileNode: (o) ->
-    {to,from} = @range
-    fromStr  =  if from then from.compile(o) else '0'
-    compiled = to?.compile o, LEVEL_PAREN
+  # We have to be careful when trying to slice through the end of the array,
+  # `9e9` is used because not all implementations respect `undefined` or `1/0`.
+  # `9e9` should be safe because `9e9` > `2**32`, the max array length.
+  compileNode: (o) ->
+    {to, from} = @range
+    fromStr    = from and from.compile(o, LEVEL_PAREN) or '0'
+    compiled   = to and to.compile o, LEVEL_PAREN
     if to and not (not @range.exclusive and +compiled is -1)
       toStr = ', ' + if @range.exclusive
-        to.compile(o)
-      else if /^[+-]?\d+$/.test compiled
-        '' + (+compiled + 1)
+        compiled
+      else if SIMPLENUM.test compiled
+        (+compiled + 1).toString()
       else
-        [definition, reference] = (ref.compile(o,LEVEL_PAREN) for ref in to.cache o)
-        # `9e9` used below because not all implementations respect `undefined` or `1/0`
-        # `9e9` should be safe because `9e9` > `2**32`, the max array length
+        [definition, reference] = to.cache o, LEVEL_PAREN
         if reference is definition
-          reference + ' + 1 ? ' + reference + ' + 1 : 9e9'
+          "#{reference} + 1 ? #{reference} + 1 : 9e9"
         else
-          '(' + definition + ' + 1) ? ' + reference + ' : 9e9'
-    ".slice(#{fromStr}#{toStr||''})"
+          "(#{definition} + 1) ? #{reference} : 9e9"
+    ".slice(#{ fromStr }#{ toStr or '' })"

 #### Obj


Added splats positional flexibility to pattern matching

--- nodes.coffee
+++ nodes.coffee
@@ -708,14 +708,21 @@
     assigns: ["$@tab$val_var = ${ value.compile(o) };"]
     o.top: true
     o.as_statement: true
+    splatted: false
     for obj, i in @variable.base.objects
       idx: i
       [obj, idx]: [obj.value, obj.variable.base] if @variable.is_object()
       access_class: if @variable.is_array() then IndexNode else AccessorNode
-      if obj instanceof SplatNode
-        val: literal(obj.compile_value(o, val_var, @variable.base.objects.indexOf(obj)))
+      if obj instanceof SplatNode and not splatted
+        val: literal(obj.compile_value(o, val_var, (oindex: @variable.base.objects.indexOf(obj)),
+          (olength: @variable.base.objects.length) - oindex - 1))
+        splatted: true
       else
-        idx: literal(idx) unless typeof idx is 'object'
+        if typeof idx isnt 'object'
+          if splatted
+            idx: literal("${val_var}.length - ${olength - idx}")
+          else
+            idx: literal(idx)
         val: new ValueNode(literal(val_var), [new access_class(idx)])
       assigns.push(new AssignNode(obj, val).compile(o))
     code: assigns.join("\n")
@@ -829,8 +836,9 @@

   # A compiling a splat as a destructuring assignment means slicing arguments
   # from the right-hand-side's corresponding array.
-  compile_value: (o, name, index) ->
-    "Array.prototype.slice.call($name, $index)"
+  compile_value: (o, name, index, trailings) ->
+    if trailings? then "Array.prototype.slice.call($name, $index, ${name}.length - $trailings)" \
+    else "Array.prototype.slice.call($name, $index)"

 # Utility function that converts arbitrary number of elements, mixed with
 # splats, to a proper array


fixes bug mentioned by @satyr in #1108

fixes bug mentioned by @satyr in #1108

"[v] = a ? b" must compile to
v = (typeof a != "undefined" && a !== null ? a : b)[0];
and not to:
v = typeof a != "undefined" && a !== null ? a : b[0];

--- nodes.coffee
+++ nodes.coffee
@@ -1301,12 +1301,12 @@

   compileExistence: (o) ->
     if @first.isComplex()
-      ref = o.scope.freeVariable 'ref'
-      fst = new Parens new Assign new Literal(ref), @first
+      ref = new Literal o.scope.freeVariable 'ref'
+      fst = new Parens new Assign ref, @first
     else
       fst = @first
-      ref = fst.compile o
-    new Existence(fst).compile(o) + " ? #{ref} : #{ @second.compile o, LEVEL_LIST }"
+      ref = fst
+    new If(new Existence(fst), ref, type: 'if').addElse(@second).compile o

   # Compile a unary **Op**.
   compileUnary: (o) ->


improve: added fall-back to the field-based eveuators for rules with instable vacuum

--- scripts-src/cells.coffee
+++ scripts-src/cells.coffee
@@ -172,7 +172,7 @@

-  analyze1: (pattern, rule, max_iters = 2048, stop_on_border_hit=true) ->
+  analyze_field_based: (pattern, rule, max_iters = 2048, stop_on_border_hit=true) ->
     throw new Error ("Pattern undefined")  unless pattern
     throw new Error ("Rule undefined")  unless rule
     #sort cells by Y, then by X
@@ -243,7 +243,8 @@

     vacuum_period = Rules.vacuum_period rule
     unless vacuum_period is 1
-      throw new Error "Empty field is not periodic for this rule. Analysis impossible"
+      #fall-back to the field-based evaluator
+      return @analyze_field_based pattern, rule, max_iters, stop_on_border_hit

     pattern = @normalize pattern


consistency++: address => address1

consistency++: address => address1

--- client/app/MainApp/payment/billingform.coffee
+++ client/app/MainApp/payment/billingform.coffee
@@ -14,7 +14,7 @@
             placeholder   : 'VAT Number (optional)'
             defaultValue  : data.vatNumber

-      address             :
+      address1            :
         label             : 'Address & ZIP'
         placeholder       : 'Address (optional)'
         defaultValue      : data.address1


wait for the group data to load before initializing the the group in the sidebar

--- client/app/MainApp/sidebar/sidebarview.coffee
+++ client/app/MainApp/sidebar/sidebarview.coffee
@@ -10,7 +10,9 @@
     @_finderExpanded  = no
     @_popupIsActive   = no

-    currentGroupData = @getSingleton('groupsController').getCurrentGroupData()
+    groupsController = @getSingleton 'groupsController'
+
+    currentGroupData = groupsController.getCurrentGroupData()

     @avatar = new AvatarView
       tagName    : "div"
@@ -45,7 +47,7 @@
     , currentGroupData

     # handle group related decisions
-    @initializeGroup()
+    groupsController.on 'GroupChanged', @bound 'initializeGroup'

     @navController = new NavigationController
       view           : new NavigationList
@@ -111,15 +113,14 @@
       @groupAvatar.setClass 'flash'
       @avatarHeader.setData currentGroupData
       @avatarHeader.show()
-#    else
-#      @avatar.unsetClass 'shared-avatar'
-#      @avatar.setWidth 160
-#      @groupAvatar.hide()
-#      @groupAvatar.unsetClass 'flash'
-#      @avatarHeader.setData currentGroupData
-#      @avatarHeader.hide()
+    else
+      @avatar.unsetClass 'shared-avatar'
+      @avatar.setWidth 160
+      @groupAvatar.hide()
+      @groupAvatar.unsetClass 'flash'
+      @avatarHeader.setData currentGroupData
+      @avatarHeader.hide()
     @render()
-

   setListeners:->


increase timeout for alarm tests

increase timeout for alarm tests

--- test/alarms_tests.coffee
+++ test/alarms_tests.coffee
@@ -56,9 +56,9 @@
                 alarm3.save done

-    it "Then I wait 10s", (done) ->
-        @timeout 14000
-        setTimeout done, 12000
+    it "Then I wait 20s", (done) ->
+        @timeout 22000
+        setTimeout done, 20 * 1000

     it "And handleNotification has been called 3 times", ->
         @counter.should.equal 3


Added gesture event helpers (onPan, onPinch, etc)

--- framer/Layer.coffee
+++ framer/Layer.coffee
@@ -898,6 +898,43 @@
 	onDragAnimationDidEnd: (cb) -> @on(Events.DragAnimationDidEnd, cb)
 	onDirectionLockDidStart: (cb) -> @on(Events.DirectionLockDidStart, cb)

+	onPan: (cb) -> @on(Events.Pan, cb)
+	onPanStart: (cb) -> @on(Events.PanStart, cb)
+	onPanMove: (cb) -> @on(Events.PanMove, cb)
+	onPanEnd: (cb) -> @on(Events.PanEnd, cb)
+	onPanCancel: (cb) -> @on(Events.PanCancel, cb)
+	onPanLeft: (cb) -> @on(Events.PanLeft, cb)
+	onPanRight: (cb) -> @on(Events.PanRight, cb)
+	onPanUp: (cb) -> @on(Events.PanUp, cb)
+	onPanDown: (cb) -> @on(Events.PanDown, cb)
+
+	onPinch: (cb) -> @on(Events.Pinch, cb)
+	onPinchStart: (cb) -> @on(Events.PinchStart, cb)
+	onPinchMove: (cb) -> @on(Events.PinchMove, cb)
+	onPinchEnd: (cb) -> @on(Events.PinchEnd, cb)
+	onPinchCancel: (cb) -> @on(Events.PinchCancel, cb)
+	onPinchIn: (cb) -> @on(Events.PinchIn, cb)
+	onPinchOut: (cb) -> @on(Events.PinchOut, cb)
+
+	onPress: (cb) -> @on(Events.Press, cb)
+	onPressUp: (cb) -> @on(Events.PressUp, cb)
+
+	onRotate: (cb) -> @on(Events.Rotate, cb)
+	onRotateStart: (cb) -> @on(Events.RotateStart, cb)
+	onRotateMove: (cb) -> @on(Events.RotateMove, cb)
+	onRotateEnd: (cb) -> @on(Events.RotateEnd, cb)
+	onRotateCancel: (cb) -> @on(Events.RotateCancel, cb)
+
+	onSwipe: (cb) -> @on(Events.Swipe, cb)
+	onSwipeLeft: (cb) -> @on(Events.SwipeLeft, cb)
+	onSwipeRight: (cb) -> @on(Events.SwipeRight, cb)
+	onSwipeUp: (cb) -> @on(Events.SwipeUp, cb)
+	onSwipeDown: (cb) -> @on(Events.SwipeDown, cb)
+
+	onTap: (cb) -> @on(Events.Tap, cb)
+	onSingleTap: (cb) -> @on(Events.SingleTap, cb)
+	onDoubleTap: (cb) -> @on(Events.DoubleTap, cb)
+
 	##############################################################
 	## DESCRIPTOR


Fix stupid bug

Fix stupid bug

--- framer/Utils.coffee
+++ framer/Utils.coffee
@@ -473,7 +473,7 @@

 	link.href = "https://fonts.googleapis.com/css?family=#{fontToLoad}"
 	link.rel = "stylesheet"
-	document.getElementsByTagName("head")[0].appendChild(link) unless TESTING
+	document.getElementsByTagName("head")[0].appendChild(link) unless window.TESTING
 	_loadedFonts.push(fontToLoad)
 	return fontObject


Better ping calls.

Better ping calls.

--- client.coffee
+++ client.coffee
@@ -186,7 +186,7 @@
       return

     if @current.message is 'ping'
-      @ping()
+      @ping @current.data
      return

     assert.equal @current.message, 'update'
@@ -215,7 +215,7 @@
   finalize: (params) ->
     throw new Error "Not implemented!"

-  ping: ->
+  ping: (data) ->
     throw new Error "Not implemented!"

   destroy: ->
@@ -250,10 +250,10 @@
     @worker.postMessage
       message: 'finalize'

-  ping: =>
+  ping: (data) =>
     @worker.postMessage
       message: 'ping'
-      data: @current.data
+      data: data

   destroy: =>
     @worker.terminate()


Fix assets path

Fix assets path

--- config/express.coffee
+++ config/express.coffee
@@ -6,9 +6,9 @@
 stylus = require('stylus')
 fluidity = require('fluidity')

-assets = __dirname + '../assets'
-
 module.exports = (app, config, passport) ->
+  assets = config.root + '/assets'
+
   app.set('showStackError', true)

   app.use(express.compress({


add robots.txt middleware

add robots.txt middleware

--- middlewares/index.coffee
+++ middlewares/index.coffee
@@ -35,6 +35,14 @@

   # http logger
   app.use logger.http
+
+  # robots.txt
+  app.use (req, res, next) ->
+    if '/robots.txt' is req.url
+      res.type 'text/plain'
+      res.send 'User-agent: *\nSitemap: /sitemap.xml\nDisallow: /dash/'
+    else
+      next()

   # parsers
   app.use bodyParser.json()


Content pages Resources, Science and links to Talk and Blog. Top nav is getting very crowded!

--- project.coffee
+++ project.coffee
@@ -18,6 +18,10 @@
   pages: [{
       'Upload': 'This will be the iRecord form.'
     },{
+      'Resources': 'Links to resources for observers.'
+    },{
+      'Science': 'The science behind the project'
+    },{
     'About': [
       {
         'Orchid Observers': 'About the project.'
@@ -26,7 +30,9 @@
       }]
   }]

-  externalLinks: {}
+  externalLinks:
+    Talk: ''
+    Blog: ''

   workflows: [{
     key: 'herbarium'


collaboration: controller remove old state machine code

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -1,4 +1,3 @@
-machina                       = require 'machina'
 _                             = require 'lodash'
 remote                        = require('app/remote').getInstance()
 dateFormat                    = require 'dateformat'
@@ -605,85 +604,6 @@

   initCollaborationStateMachine: ->

-    @stateMachine = new machina.Fsm
-      initialState: 'uninitialized'
-      states:
-        uninitialized:
-          _onEnter: @bound 'onCollaborationUninitialized'
-        loading:
-          _onEnter: @bound 'onCollaborationLoading'
-        active:
-          _onEnter: @bound 'onCollaborationActive'
-        terminated:
-          _onEnter: @bound 'onCollaborationTerminated'
-        notAuthorized:
-          _onEnter: @bound 'onCollaborationNotAuthorized'
-
-
-  onCollaborationUninitialized: ->
-
-    @rtm = new RealtimeManager
-    @showShareButton()
-    kd.utils.defer => @rtm.ready => @setCollaborationState 'loading'
-
-
-  onCollaborationLoading: do ->
-    constraints =
-      channelReady  : null
-      sessionActive : null
-
-    conditions = [
-      when : -> (constraints.channelReady is no) or (constraints.sessionActive is no)
-      to   : 'terminated'
-    ,
-      when : -> (constraints.channelReady is yes) and (constraints.sessionActive is yes)
-      to   : 'active'
-    ]
-    nextIfReady = (context) ->
-      for condition in conditions when condition.when()
-        context.setCollaborationState condition.to
-        break
-    setConstraint = (context, key, value) ->
-      constraints[key] = Boolean value
-      nextIfReady context
-    ->
-
-      @statusBar.emit 'CollaborationLoading'
-
-      { channelId } = @workspaceData
-
-      unless channelId
-        setConstraint this, 'channelReady', no
-        return
-
-      @fetchSocialChannel (err, channel) =>
-        if err or not channel
-          setConstraint this, 'channelReady', no
-          throwError err  if err
-          return
-
-        setConstraint this, 'channelReady', yes
-
-        @isRealtimeSessionActive channelId, (isActive) =>
-          result = isActive or @isInSession
-          setConstraint this, 'sessionActive', result
-
-
-  onCollaborationActive: ->
-
-    @startChatSession => @chat.showChatPane()
-    @chat.hide()
-    @statusBar.emit 'CollaborationStarted'
-    @collectButtonShownMetric()
-
-
-  onCollaborationTerminated: ->
-
-    @statusBar.emit 'CollaborationEnded'
-    @collectButtonShownMetric()
-
-
-  onCollaborationNotAuthorized: ->

   prepareCollaboration: ->


Flat out callback functions as arguments

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -707,19 +707,16 @@
     @chat.settingsPane.endSession.disable()

     if @amIHost
-      @endCollaborationForHost
-        success: =>
-          @modal?.destroy()
-          @handleCollaborationEndedForHost()
-
+      @endCollaborationForHost =>
+        @modal?.destroy()
+        @handleCollaborationEndedForHost()
     else
-      @endCollaborationForParticipant
-        success: =>
-          @modal?.destroy()
-          @handleCollaborationEndedForParticipant()
-
-
-  endCollaborationForHost: (callbacks) ->
+      @endCollaborationForParticipant =>
+        @modal?.destroy()
+        @handleCollaborationEndedForParticipant()
+
+
+  endCollaborationForHost: (callback) ->

     @broadcastMessage { type: 'SessionEnded' }

@@ -737,7 +734,7 @@
       envHelpers.detachSocialChannel @workspaceData, (err) =>
         throwError err  if err

-    callbacks.success()
+    callback()

   handleCollaborationEndedForHost: ->
@@ -756,7 +753,7 @@
     @cleanupCollaboration()

-  endCollaborationForParticipant: (callbacks) ->
+  endCollaborationForParticipant: (callback) ->

     @broadcastMessage { type: 'ParticipantWantsToLeave' }

@@ -766,7 +763,7 @@
     @setMachineUser [nick()], no, =>
       throwError err  if err

-    callbacks.success()
+    callback()

   handleCollaborationEndedForParticipant: ->


Refactor "resurrectSnapshot" method.

Refactor "resurrectSnapshot" method.

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -237,7 +237,7 @@
   activateRealtimeManagerForParticipant: ->

     @startRealtimePolling()
-    @resurrectSnapshot()
+    @resurrectParticipantSnapshot()

     if @permissions.get(nick()) is 'read'
       @makeReadOnly()
@@ -578,17 +578,15 @@
   ###*
    * Resurrect snapshot for participant
   ###
-  resurrectSnapshot: ->
+  resurrectParticipantSnapshot: ->

     @whenRealtimeReady =>

       @removeInitialViews()
-
-      mapLength = @myWatchMap?.values().length
-
-      # Check it while `myWatchMap` is unfilled
-      # or the participant isn't following the host.
-      if not mapLength or (mapLength and @amIWatchingChangeOwner(@collaborationHost))
+      mapLength = @myWatchMap.values()?.length
+
+      ## `mapLength=0` meant the participant joined the collaboration just now.
+      if not mapLength or @amIWatchingChangeOwner(@collaborationHost)
         @getHostSnapshot (snapshot) =>
           @layoutManager.resurrectSnapshot snapshot, yes
       else


collaboration: fix share button metric collection places

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -689,7 +689,11 @@
     @ready =>
       @statusBar.handleCollaborationLoading()
       @statusBar.share.show()
-      # IDEMetrics.collect 'StatusBar.collaboration_button', 'shown'
+
+
+  collectButtonShownMetric: ->
+
+    IDEMetrics.collect 'StatusBar.collaboration_button', 'shown'

   prepareCollaboration: ->
@@ -702,10 +706,12 @@
     @rtm.ready =>
       unless @workspaceData.channelId
         @statusBar.emit 'CollaborationEnded'
+        @collectButtonShownMetric()

       @fetchSocialChannel (err, channel) =>
         if err or not channel
           @statusBar.emit 'CollaborationEnded'
+          @collectButtonShownMetric()
           throwError err  if err
           return

@@ -714,6 +720,7 @@
             @startChatSession => @chat.showChatPane()
             @chat.hide()
             @statusBar.emit 'CollaborationStarted'
+            @collectButtonShownMetric()

   startCollaborationSession: (callback) ->


detect extra proxy url cases

detect extra proxy url cases

--- app/assets/javascripts/uploadcare/settings.coffee
+++ app/assets/javascripts/uploadcare/settings.coffee
@@ -207,11 +207,11 @@
     if settings.previewProxy and not settings.previewUrlCallback
       settings.previewUrlCallback = (url, info) =>
         encodedUrl = encodeURIComponent(url)
-        namedArg = /\=$/.test(settings.previewProxy)
-        ampersandLast = /\&$/.test(settings.previewProxy)
-
-        path = if namedArg then encodedUrl else
-          (if ampersandLast then "url=#{encodedUrl}" else "&url=#{encodedUrl}")
+        raw = /\=$/.test(settings.previewProxy)
+        namedNoAmp = /[\&\?]$/.test(settings.previewProxy)
+
+        path = if raw then encodedUrl else
+          if namedNoAmp then "url=#{encodedUrl}" else "&url=#{encodedUrl}"

         utils.normalizeUrl(settings.previewProxy) + path


optimized page reload

optimized page reload

--- platform/src/app.coffee
+++ platform/src/app.coffee
@@ -402,6 +402,13 @@
 ### Main ###

 $(document).ready ->
+  # A hash needs to be set, or we won't be able to load the code
+  if not window.location.search
+    # Reload the page
+    return window.location.search = "?optics/"
+
+  programId = window.location.search.slice(1)
+
   setupLayout()
   setupButtonHandlers()

@@ -415,10 +422,6 @@
   log = setupEditor("log")
   log.setReadOnly(true)
   resetLogContent()
-
-  # A hash needs to be set, or we won't be able to load the code
-  if not window.location.search then window.location.search = "?optics/"
-  programId = window.location.search.slice(1)

   # Offer to load code from the cache if we can
   loadedCode = false


added serializeGameJson()

added serializeGameJson()

--- platform/src/app.coffee
+++ platform/src/app.coffee
@@ -220,6 +220,8 @@
       # Execute again
       executeCode()

+  $("#saveButton").on("click", -> console.log("Game JSON: #{serializeGameJson()}"))
+
 # Mode should be something that ACE Editor recognizes, like "ace/mode/javascript"
 setupEditor = (id, mode = "") ->
   editor = ace.edit(id)
@@ -494,6 +496,12 @@
     data: matches[3]
   }

+serializeGameJson = ->
+  gameJson = {}
+  for property, editorId of GAME_JSON_PROPERTY_TO_EDITOR
+    gameJson[property] = editors[editorId].getValue()
+  return JSON.stringify(gameJson, null, 2)
+

 ### Main ###


make sure to use base dir name

make sure to use base dir name

--- gist-content.coffee
+++ gist-content.coffee
@@ -82,7 +82,7 @@
   return gistCb() if singleId && (gist.id != singleId)
   #console.log "NOT RETURNING", gist.id, singleId
   fileNames = Object.keys gist.files
-  folder = "data/gists/" + gist.id
+  folder = base + gist.id
   fs.mkdir folder, ->
   async.each fileNames, (fileName, fileCb) ->
     ext = path.extname(fileName).toLowerCase()


Upgraded the devdeploy docs to latest

Upgraded the devdeploy docs to latest

--- scripts/devdeploy.coffee
+++ scripts/devdeploy.coffee
@@ -1,3 +1,14 @@
+# Description:
+#   Does deploys to dev machines and manages their states
+#
+# Commands:
+#   hubot devdeploy <branchname> - Deploys a given branch to an available dev machine
+#   hubot devdeploy status - Displays the status of the available dev machines
+#   hubot devdeploy unlock <machinename> - Unlock machines dev 1 through dev 3
+#
+# Author:
+#   adnan
+
 jenkinsURL           = process.env.HUBOT_CIBOT_URL
 jenkinsToken         = process.env.HUBOT_CIBOT_BUILD_TOKEN
 jenkinsBuildJobName  = process.env.HUBOT_CIBOT_DEV_BUILD_JOB_NAME


Add prompts: false

Add prompts: false

--- docpad.coffee
+++ docpad.coffee
@@ -1,4 +1,6 @@
 module.exports =
+
+  prompts: false

   # These are variables will be accessible via our templates
   templateData:


changed incorrect meta data

changed incorrect meta data

--- docpad.coffee
+++ docpad.coffee
@@ -13,7 +13,7 @@
 		# Specify some site properties
 		site:
 			# The production url of our website
-			url: "http://csell.net"
+			url: "http://www.csell.net"

 			# Here are some old site urls that you would like to redirect from
 			oldUrls: [


reduce line length

reduce line length

--- app/assets/javascripts/mtl/document_modal.coffee
+++ app/assets/javascripts/mtl/document_modal.coffee
@@ -74,7 +74,8 @@
     when isPDF(filename) && PDFObject.supportsPDFs
       PDFObject.embed(url, $container, { width: '800px', height: $(window).height() + 'px' })
     else
-      $container.append(MTL.renderTemplate('no_preview', { title: title, filename: filename, url: url }))
+      data = { title: title, filename: filename, url: url }
+      $container.append(MTL.renderTemplate('no_preview', data))

 findOrCreateDocumentModal = ->
   return $('<div/>').appendTo('body') unless $('body > .document-modal').first().length


fix popover

fix popover

--- app/assets/javascripts/pt/tags/popover.js.coffee
+++ app/assets/javascripts/pt/tags/popover.js.coffee
@@ -6,7 +6,6 @@
     callback: ->
       $el = $ this
       $el.data 'ptPopoverFocused', true
-      log $el.data()
       attr = $el.ptAttr()

       if not $el.data 'ptPopoverLoaded'
@@ -32,5 +31,9 @@
     event: 'mouseleave'
     callback: ->
       $el = $ this
+      attr = $el.ptAttr()
       $el.data 'ptPopoverFocused', false
-      $el.popover 'hide'
+      if not attr.popoverContainer
+        $el.popover 'hide'
+      else
+        $el.closest(attr.popoverContainer).find('.popover').remove()


Found how to turn on the AntiAliasing!

Found how to turn on the AntiAliasing!

--- app/assets/javascripts/roofpig/Display.js.coffee
+++ app/assets/javascripts/roofpig/Display.js.coffee
@@ -10,7 +10,7 @@
   constructor: ->
     @input_handler = new InputHandler()

-    @renderer = new THREE.WebGLRenderer()
+    @renderer = new THREE.WebGLRenderer({ antialias: true })
     @renderer.setSize window.innerWidth, window.innerHeight
     document.body.appendChild @renderer.domElement


remove unnecessary object Copy

remove unnecessary object Copy

--- app/assets/javascripts/stores/score_store.coffee
+++ app/assets/javascripts/stores/score_store.coffee
@@ -18,7 +18,7 @@
     threshold = if type is 'clear' then 4 else 2
     remain = 0
     for id, _ of SheetStore.get()
-      score = objectCopy scores[id]
+      score = scores[id]
       score ||=
         state: 7
       remain++ if threshold < score.state


fix invalid syntax in ContentMigration.coffee

Change-Id: Ic426afc3de47a0bf988aad1e93d13cfd5a2d118c
Reviewed-on: https://gerrit.instructure.com/141720
Tested-by: Jenkins
Reviewed-by: Jacob Fugal <f862f167b85d41b225785c70d70808bc7337c1fe@instructure.com>
Reviewed-by: Ryan Shaw <ea3cd978650417470535f3a4725b6b5042a6ab59@instructure.com>
Product-Review: Ryan Shaw <ea3cd978650417470535f3a4725b6b5042a6ab59@instructure.com>
QA-Review: Ryan Shaw <ea3cd978650417470535f3a4725b6b5042a6ab59@instructure.com>

--- app/coffeescripts/models/ContentMigration.coffee
+++ app/coffeescripts/models/ContentMigration.coffee
@@ -102,9 +102,9 @@
       super _.omit(arguments[0], 'file'),
         error: (xhr) => reject(xhr.responseText)
         success: (model, xhr, options) =>
-          uploader.completeUpload(@get('pre_attachment'), file, ignoreResult: true).
-            catch(reject).
-            then(resolve)
+          uploader.completeUpload(@get('pre_attachment'), file, ignoreResult: true)
+            .catch(reject)
+            .then(resolve)

       dObject


scroll to link anchor after wiki page renders

test plan:
 - create a wiki page with several screenfuls of text, and put
   a named anchor in the middle of the text

 - go directly to the page and anchor by entering something like
   this into your address bar:
     https://<canvas>/courses/X/pages/page_name#somewhere
 - after the page text loads, it should scroll to make
   the text following that anchor visible
 - repeat using an element id instead of a named anchor;
   for example

 - test in all major browsers (Firefox and IE don't need this
   hack, but it should be harmless; otoh Chrome and Safari won't
   automatically scroll to an anchor that didn't exist on page load)
 - also be sure wiki pages load if you _don't_ provide a #hashthing
   at the end of the request URL

fixes CNVS-13690

Change-Id: I2cc8fdd7b5a655cd07d260ffacbb11c06e753848
Reviewed-on: https://gerrit.instructure.com/50532
Reviewed-by: Clay Diffrient <9dff2e5c98626d20d2848250c411e8009465bb87@instructure.com>
QA-Review: Jahnavi Yetukuri <ed2b2672ccc2e59e56c0615df7f01b579202e014@instructure.com>
Tested-by: Jenkins
Product-Review: Jeremy Stanley <b3f594e10a9edcf5413cf1190121d45078c62290@instructure.com>

--- app/coffeescripts/views/wiki/WikiPageView.coffee
+++ app/coffeescripts/views/wiki/WikiPageView.coffee
@@ -69,8 +69,17 @@
         @$sequenceFooter?.msfAnimation(false)
       @$sequenceFooter.appendTo(@$el) if @$sequenceFooter

+    navigateToLinkAnchor: ->
+      anchor_name = window.location.hash.replace(/^#/, "")
+      if anchor_name.length
+        $anchor = $("#wiki_page_show .user_content ##{anchor_name}")
+        $anchor = $("#wiki_page_show .user_content a[name='#{anchor_name}']") unless $anchor.length
+        if $anchor.length
+          $('html, body').scrollTo($anchor)
+
     afterRender: ->
       super
+      @navigateToLinkAnchor()
       @reloadView = new WikiPageReloadView
         el: @$pageChangedAlert
         model: @model


Forgotten one 'restrict to serializable' call.

--- app/lib/dom_text_matcher/dom_text_matcher.coffee
+++ app/lib/dom_text_matcher/dom_text_matcher.coffee
@@ -1,12 +1,6 @@
 class window.DomTextMatcher

   # ===== Public methods =======
-
-  # Switch the library into "serializable-only" mode.
-  # If set to true, all public API calls will be restricted to return
-  # strictly serializable data structures.
-  # (References to DOM objects will be omitted.)
-  restrictToSerializable: (value = true) -> @mapper.restrictToSerializable value

   # Consider only the sub-tree beginning with the given node.
   #


All tests run correctly now

All tests run correctly now

--- test/app_test.coffee
+++ test/app_test.coffee
@@ -5,9 +5,9 @@
 describe 'General', ->
   describe 'Front page', ->
     it "should be here", (done) ->
+      @timeout(10000)
       request(app)
-        .get("/")
-        .send("{}")
+        .get('/')
         .expect(200, done)
   describe '404 Routing', ->
     it 'should not be here', (done) ->


Use BufferedProcess for apm commands

Use BufferedProcess for apm commands

This ensures the path to node is always available in the environment.

--- src/app/package-manager.coffee
+++ src/app/package-manager.coffee
@@ -1,4 +1,4 @@
-{spawn} = require 'child_process'
+BufferedProcess = require 'buffered-process'
 roaster = require 'roaster'
 async = require 'async'

@@ -22,14 +22,14 @@
   queue.drain = callback

 getAvailable = (callback) ->
-  apm = require.resolve '.bin/apm'
-  apmProcess = spawn(apm, ['available', '--json'])
-  chunks = []
-  apmProcess.stdout.on 'data', (chunk) -> chunks.push(chunk)
-  apmProcess.on 'close', (code) ->
+  command = require.resolve '.bin/apm'
+  args = ['available', '--json']
+  output = []
+  stdout = (lines) -> output.push(lines)
+  exit = (code) ->
     if code is 0
       try
-        packages = JSON.parse(Buffer.concat(chunks).toString()) ? []
+        packages = JSON.parse(output.join()) ? []
       catch error
         callback(error)
         return
@@ -41,15 +41,17 @@
     else
       callback(new Error("apm failed with code: #{code}"))

+  new BufferedProcess({command, args, stdout, exit})
+
 install = ({name, version}, callback) ->
   activateOnSuccess = !atom.isPackageDisabled(name)
   activateOnFailure = atom.isPackageActive(name)
   atom.deactivatePackage(name) if atom.isPackageActive(name)
   atom.unloadPackage(name) if atom.isPackageLoaded(name)

-  apm = require.resolve '.bin/apm'
-  apmProcess = spawn(apm, ['install', "#{name}@#{version}"])
-  apmProcess.on 'close', (code) =>
+  command = require.resolve '.bin/apm'
+  args = ['install', "#{name}@#{version}"]
+  exit = (code) ->
     if code is 0
       atom.activatePackage(name) if activateOnSuccess
       callback()
@@ -57,16 +59,20 @@
       actom.activatePackage(name) if activateOnFailure
       callback(new Error("Installing '#{name}' failed."))

+  new BufferedProcess({command, args, exit})
+
 uninstall = ({name}, callback) ->
   atom.deactivatePackage(name) if atom.isPackageActive(name)

-  apm = require.resolve '.bin/apm'
-  apmProcess = spawn(apm, ['uninstall', name])
-  apmProcess.on 'close', (code) =>
+  command = require.resolve '.bin/apm'
+  args = ['uninstall', name]
+  exit = (code) ->
     if code is 0
       atom.unloadPackage(name) if atom.isPackageLoaded(name)
       callback()
     else
       callback(new Error("Uninstalling '#{name}' failed."))

+  new BufferedProcess({command, args, exit})
+
 module.exports = {renderMarkdownInMetadata, install, uninstall, getAvailable}


temp migrate set management analytics

temp migrate set management analytics

--- components/favorites2/client/analytics.coffee
+++ components/favorites2/client/analytics.coffee
@@ -6,11 +6,10 @@
 # See: http://cl.ly/image/3q040w1J2I2y/o
 #

-{ track } = analytics = require '../../../lib/analytics.coffee'
 Cookies = require 'cookies-js'
 mediator = require '../../../lib/mediator.coffee'

-MODE = Cookies.get('save-controls') or analytics.getProperty('ab:save:controls')
+MODE = Cookies.get('save-controls')

 # Funnel 1 (NUX Onboarding modal to new set made)
 # ------------------------------------------------------------------------------
@@ -18,25 +17,25 @@
 # NUX modal
 mediator.on 'modal:opened', ({ view }) ->
   return unless view.$el.html().match('favorites2-onboarding-modal')?
-  track.funnel 'Set Management: NUX modal'
+  analytics.track 'Set Management: NUX modal'

 # Click
 $(document).on 'click', '.favorites2-onboarding-modal a', ->
-  track.funnel 'Set Management: NUX modal - click'
+  analytics.track 'Set Management: NUX modal - click'

 # Dismiss
 mediator.on 'modal:closed', ({ view }) ->
   return unless view.$el.html().match('favorites2-onboarding-modal')?
-  track.funnel 'Set Management: NUX modal - dismiss'
+  analytics.track 'Set Management: NUX modal - dismiss'

 # Click create new set
 $(document).on 'click', '.favorites2-new-collection', ->
-  track.funnel 'Set Management: Click create new set'
+  analytics.track 'Set Management: Click create new set'

 # New set made
 onNewSet = (e) ->
   return if e.keyCode and e.keyCode isnt 13
-  track.funnel 'Set Management: New set made'
+  analytics.track 'Set Management: New set made'
 $(document).on 'keyup', '.favorites2-edit-modal input', onNewSet
 $(document).on 'click', '.favorites2-edit-modal-save', onNewSet

@@ -52,50 +51,50 @@
     return if ($(e.target).closest(
                  '.favorites2-collection-list-container'
               ).length > 0 and not $(e.target).hasClass('.modal-close'))
-    track.funnel 'Set Management: Open menu - dismiss'
+    analytics.track 'Set Management: Open menu - dismiss'

 # Hover on work
 $(document).on 'mouseenter', '.artwork-item', ->
   hoverTimeout = setTimeout ->
     hoverTimeout = null
-    track.funnel 'Set Management: Hover on work'
+    analytics.track 'Set Management: Hover on work'
   , 300

 # Click remove
 $(document).on 'click', '.artwork-item-remove', ->
   clickedSaveControl = true
-  track.click 'Set Management: Click remove'
+  analytics.track 'Set Management: Click remove'

 # Click favicon
 $(document).on 'click', '.overlay-button-save, .circle-icon-button-save', (e) ->
   clickedSaveControl = true
-  track.funnel 'Set Management: Click favicon'
+  analytics.track 'Set Management: Click favicon'
   if $(e.target).closest('.save-controls-one-btn-container').length
     listenForMenuDismiss()
   if $(e.target).closest('.save-controls-two-btn-container').length
-    track.funnel 'Set Management: Successful save'
+    analytics.track 'Set Management: Successful save'

 # Click set stack
 $(document).on 'click', ('.save-controls-two-btn-add-to-collection, ' +
                          '.circle-icon-button-add'), ->
   clickedSaveControl = true
   openedMenu = true
-  track.funnel 'Set Management: Click set stack'
+  analytics.track 'Set Management: Click set stack'
   listenForMenuDismiss()

 # Dismiss
 $(document).on 'mouseleave', '.artwork-item', (e) ->
   return clearTimeout(hoverTimeout) if hoverTimeout
   unless clickedSaveControl
-    track.funnel 'Set Management: Hover on work - dismiss'
+    analytics.track 'Set Management: Hover on work - dismiss'
   clickedSaveControl = false

 # Work saved to My Favs
 $(document).on 'click', ('.save-controls-drop-down-container ' +
                          '.overlay-button-save, .circle-icon-button-save'), ->
   return unless MODE is 'drop down'
-  track.funnel 'Set Management: Work Saved to My Favs'
-  track.funnel 'Set Management: Successful save'
+  analytics.track 'Set Management: Work Saved to My Favs'
+  analytics.track 'Set Management: Successful save'
   listenForMenuDismiss()

 # Added to set X
@@ -105,26 +104,26 @@
   '.favorites2-collection-list li:not(.is-active)'
 ].join(','), (e) ->
   addedToSet = true
-  track.funnel 'Set Management: Add to set',
+  analytics.track 'Set Management: Add to set',
     setId: $(e.currentTarget).attr('data-id')
-  track.funnel 'Set Management: Successful save'
+  analytics.track 'Set Management: Successful save'
   # Added to an additional set
   if $(e.currentTarget).siblings().hasClass('is-active')
-    track.funnel 'Set Management: Add to set - additional set'
+    analytics.track 'Set Management: Add to set - additional set'

 # Removed from set X
 $(document).on 'click', [
   '.save-controls-drop-down-menu-item.is-active'
   '.favorites2-collection-list > li.is-active'
 ].join(','), (e) ->
-  track.funnel 'Set Management: Remove from set',
+  analytics.track 'Set Management: Remove from set',
     inline: true
     setId: $(e.currentTarget).attr('data-id')

 # Name Create new set
 onNewSetInline = (e) ->
   return if e.keyCode and e.keyCode isnt 13
-  track.funnel 'Set Management: Name Create new set'
+  analytics.track 'Set Management: Name Create new set'
 $(document).on(
   'keyup', '.favorites2-collection-list-create input', onNewSetInline
 )
@@ -141,24 +140,24 @@
 # Remove via X in corner
 $(document).on 'click', ('.favorites2-edit-modal-delete-confirm'), (e) ->
   return unless $(e.target).text() is 'Remove work'
-  track.click 'Set Management: Remove from set', inline: false
+  analytics.track 'Set Management: Remove from set', inline: false

 # Delete set
 $(document).on 'click', ('.favorites2-edit-modal-init + ' +
                          '.favorites2-edit-modal-delete-container' +
                          'favorites2-edit-modal-delete-confirm'), ->
-  track.click 'Set Management: Delete set'
+  analytics.track 'Set Management: Delete set'

 # Privacy switch
 $(document).on 'click', '.favorites2-toggle-public', ->
-  track.click 'Set Management: Make private'
+  analytics.track 'Set Management: Make private'
 $(document).on 'click', '.favorites2-toggle-private', ->
-  track.click 'Set Management: Make public'
+  analytics.track 'Set Management: Make public'

 # Slideshow
 $(document).on 'click', '#user-profile-collection-right-slideshow', ->
-  track.click 'Set Management: Slideshow'
+  analytics.track 'Set Management: Slideshow'

 # Share set
 $(document).on 'click', '#user-profile-collection-right-share', ->
-  track.click 'Set Management: Share set'+  analytics.track 'Set Management: Share set'


Remove onAuthorSelect test

Remove onAuthorSelect test

--- client/apps/edit/components/admin/test/index.coffee
+++ client/apps/edit/components/admin/test/index.coffee
@@ -90,14 +90,6 @@
       )
       (@view.article.featuredArtists.get('bar')?).should.be.ok

-  describe '#onAuthorSelect', ->
-
-    it 'changes the author', ->
-      global.confirm = -> true
-      @view.onAuthorSelect {}, { id: 'foo' }
-      @view.article.get('author_id').should.equal 'foo'
-      delete global.confirm
-
   describe '#toggleScheduled', ->

     it 'can save the scheduled publish date and time', ->


take referer off window

take referer off window

--- lib/assets/javascripts/angular-turbolinks.js.coffee
+++ lib/assets/javascripts/angular-turbolinks.js.coffee
@@ -3,6 +3,7 @@
   loadedAssets = null
   createDocument = null
   xhr_req = null
+  referer = null

   triggerEvent = (name, data) ->
     event = document.createEvent 'Events'
@@ -16,7 +17,7 @@
     value

   rememberReferer = ->
-    window.referer = document.location.href
+    referer = document.location.href

   processResponse = (responseText, status, headers)->
     clientOrServerError = ->
@@ -130,7 +131,7 @@
       method: 'GET',
       headers: {
         'Accept' : 'text/html, application/xhtml+xml, application/xml',
-        'X-XHR-Referer' : window.referer
+        'X-XHR-Referer' : referer
       },
       timeout: xhr_req.promise
     }).success((data, status, headers)->


Tests: Collaboration: Helpers: Use better class name.

--- client/test/lib/helpers/collaborationhelpers.coffee
+++ client/test/lib/helpers/collaborationhelpers.coffee
@@ -10,7 +10,7 @@

     shareButtonSelector = '.status-bar a.share:not(.loading)'
     chatViewSelector    = '.chat-view.onboarding'
-    startButtonSelector = '.chat-view.onboarding .buttons button.green'
+    startButtonSelector = '.chat-view.onboarding .buttons button.start-session'

     helpers.beginTest(browser)
     helpers.waitForVMRunning(browser)


Replaced unnecessary filename/temp file with hard coded value

--- src/beautifiers/rubocop.coffee
+++ src/beautifiers/rubocop.coffee
@@ -5,7 +5,6 @@
 "use strict"
 Beautifier = require('./beautifier')
 path = require('path')
-temp = require('temp').track()

 module.exports = class Rubocop extends Beautifier
   name: "Rubocop"
@@ -20,7 +19,7 @@

   beautify: (text, language, options, context) ->
     fullPath = context.filePath
-    [projectPath, relativePath] = atom.project.relativizePath(fullPath)
+    [projectPath, _relativePath] = atom.project.relativizePath(fullPath)

     # Find the rubocop path
     @Promise.all([
@@ -47,7 +46,7 @@
       rubocopArguments = [
         "--auto-correct"
         "--force-exclusion"
-        "--stdin", relativePath || temp.path({suffix: '.rb'})
+        "--stdin", "atom-beautify.rb" # filename is required but not used
       ]
       rubocopArguments.push("--config", tempConfig) if tempConfig?
       @debug("rubocop arguments", rubocopArguments)


port 3 more tests

port 3 more tests

--- test/compiler.coffee
+++ test/compiler.coffee
@@ -35,6 +35,35 @@
     a:
       .ref: x
   """
+,
+  label: 'Single Rule with no brackets'
+  grammar: """
+    a: x
+  """
+  yaml: """
+    a:
+      .ref: x
+  """
+,
+  label: 'Single Rule With Trailing Quantifier'
+  grammar: """
+    a: <x>*
+  """
+  yaml: """
+    a:
+      +min: 0
+      .ref: x
+  """
+,
+  label: 'Single Rule With Trailing Quantifier (no angles)'
+  grammar: """
+    a: x*
+  """
+  yaml: """
+    a:
+      +min: 0
+      .ref: x
+  """
 ]

 tests = []


allow for : after macro definition; fix {} error coloring


now we only requestAnimationFrame after a succesful get

--- examples/audio/static/coffee/spectrogram.coffee
+++ examples/audio/static/coffee/spectrogram.coffee
@@ -74,7 +74,7 @@

     @render()

-  request_data: () =>
+  request_data: () ->
     $.ajax 'http://localhost:5000/data',
     type: 'GET'
     dataType: 'json'
@@ -82,13 +82,14 @@
       #console.log "AJAX Error: #{textStatus}"
     success: (data, textStatus, jqXHR) =>
       @on_data(data)
+      requestAnimationFrame(
+        => @request_data())
+
     new_render = new Date()
     console.log("render_time", new_render - @last_render)
     @last_render = new_render
-    requestAnimationFrame(
-      => @request_data())
-
-  on_data: (data) =>
+
+  on_data: (data) ->
     if not data[0]?
       return

@@ -163,7 +164,7 @@
     })
     @hist_source.trigger('change', @power_source, {})

-  set_freq_range : (event, ui) =>
+  set_freq_range : (event, ui) ->
     [min, max] = ui.values
     @fft_xrange.set({'start': min, 'end' : max})
     return null


Removed "Copy To Clipboard" button

Removed "Copy To Clipboard" button

--- views/navigation-bar.cjsx
+++ views/navigation-bar.cjsx
@@ -101,8 +101,6 @@
             <Button bsSize='small' bsStyle='primary' onClick={@maximiseWindow} onContextMenu={@resetWindow} title='Zoom/Reset Window'><FontAwesome name='arrows' /></Button>
             <Button bsSize='small' bsStyle='primary' onClick={@toggleFullscreen} title='Toggle Fullscreen'><FontAwesome name='arrows-alt' /></Button>
           </ButtonGroup>
-          <span>　</span>
-          <Button bsSize='small' onClick={@copyToClipboard} title='Copy To Clipboard'><FontAwesome name='clipboard' /></Button>
         </Col>
         <Col lg={1} md={1} xs={1}>
           <Button bsSize='small' onClick={@hideWindow} title='Hide Window'><FontAwesome name='times' /></Button>


Remove unused constant var

Remove unused constant var

--- src/components/book-content-mixin.cjsx
+++ src/components/book-content-mixin.cjsx
@@ -16,10 +16,6 @@
   '[data-type=footnote-ref]'
   '[data-targeted=media]'
 ]
-
-# elements that are currently targeted by the URL's hash are
-# scrolled down this much in order to appear below the top navbar
-FOCUSED_TARGET_TOP_POSITION = 100

 MEDIA_LINK_SELECTOR = _.reduce(MEDIA_LINK_EXCLUDES, (current, exclude) ->
   "#{current}:not(#{exclude})"


debug

debug

--- dev/enc.iced
+++ dev/enc.iced
@@ -2,11 +2,11 @@
 {Decryptor} = require '../lib/dec'

 key = new Buffer "this be the password"
-data = new Buffer ("this be the secret message" for i in [0..500]).join " -> "
+data = new Buffer ("this be the secret message" for i in [0..5]).join " -> "

-enc = new Encryptor { key }
+enc = new Encryptor { key, version : 3 }
 dec = new Decryptor { key }
-for i in [0...100]
-  await enc.run { data : new Buffer data }, defer err, ct
-  await dec.run { data : ct }, defer err, pt
-  console.log pt.toString()
+await enc.run { data : new Buffer data }, defer err, ct
+console.log key.toString('hex')
+console.log ct.toString('hex')
+process.exit(0)


Taptool callback fix.

Taptool callback fix.

Taptool was trying to execute action that was renamed to callback in PR #2675

--- bokehjs/src/coffee/tool/gestures/tap_tool.coffee
+++ bokehjs/src/coffee/tool/gestures/tap_tool.coffee
@@ -17,13 +17,13 @@
       vy: vy
     }

-    action = @mget("action")
+    callback = @mget("callback")

     for r in @mget('renderers')
       ds = r.get('data_source')
       sm = ds.get('selection_manager')
       sm.select(@, @plot_view.renderers[r.id], geometry, final, append)
-      if action? then action.execute(ds)
+      if callback? then callback.execute(ds)

     @_save_geometry(geometry, final, append)
     return null
@@ -38,4 +38,4 @@

 module.exports =
   Model: TapTool
-  View: TapToolView+  View: TapToolView


extract 'publish' method

extract 'publish' method

--- zechat/static/identity.coffee
+++ zechat/static/identity.coffee
@@ -31,22 +31,31 @@

 class zc.Identity extends Backbone.Marionette.Controller

+  initialize: ->
+    @model = @options.app.request('identity')
+
   createView: ->
-    model = @options.app.request('identity')
-    view = new zc.IdentityView(model: model)
+    view = new zc.IdentityView(model: @model)

     view.on 'click-publish', =>
-      url = @options.app.request('urls').post_identity
-      data = {
-        fingerprint: model.get('fingerprint')
-        public_key: zc.get_public_key(model.get('key'))
-      }
-      zc.post_json url, data, (resp) =>
-        model.set('public_url', resp.url)
+      @publish()
+      .done ->
         view.render()

     view.on 'click-delete', =>
-      model.clear()
+      @model.clear()
       window.location.reload()

     return view
+
+  publish: ->
+    url = @options.app.request('urls').post_identity
+    data = {
+      fingerprint: @model.get('fingerprint')
+      public_key: zc.get_public_key(@model.get('key'))
+    }
+
+    Q(zc.post_json url, data)
+    .then (resp) =>
+      @model.set('public_url', resp.url)
+      return resp.url


Tiny fix for custard as non-root. Main fix is in charm

--- server/code/index.coffee
+++ server/code/index.coffee
@@ -801,7 +801,6 @@
 server = app.listen port, ->
   if existsSync(port)
     fs.chmodSync port, 0o660
-    child_process.exec "chown :www-data #{port}"
   console.log "Listening on #{port}\nPress CTRL-C to stop server."

 # Wait for all connections to finish before quitting


chore(build): St the correct paths for the files to be linted.

--- grunt/jshint.coffee
+++ grunt/jshint.coffee
@@ -1,4 +1,4 @@
 module.exports =
   options:
     reporter: require('jshint-stylish')
-  all: ['**/*.js', '!node_modules/**/*.js']
+  all: ['src/**/*.js', 'util/**/*.js', 'test/**/*.js', '!test/coverage/**/*.js']


Wrap store stuff in a run block.

Wrap store stuff in a run block.

--- test/javascripts/support/test_initializer.js.coffee
+++ test/javascripts/support/test_initializer.js.coffee
@@ -22,7 +22,7 @@
 )

 # All interactions with ember are while a user is signed in
-@currentUserId = 183475
+@currentUserId = 1
 @fakeUser = ETahi.Factory.createRecord 'User',
   id: @currentUserId
   full_name: "Fake User"


fix: test case

fix: test case

--- tests/integration/components/api-filter-test.coffee
+++ tests/integration/components/api-filter-test.coffee
@@ -13,4 +13,4 @@

   @render hbs """{{api-filter}}"""

-  assert.equal @$().text().trim(), 'API Scanner URL FilterEnter the API endpoint to scan: eg. api.appknox.com. Do not specify the scheme (http://...) or path (.../users).Add Filter'
+  assert.equal @$().text().trim(), 'API Scanner URL FilterEnter the API endpoint to scan: eg. api.appknox.com. Do not specify the scheme (http://...) or path (.../users)add new urlSave Filter'


Really, really fixed singular/plural followers.

--- core/app/backbone/views/sidebar_profile_view.coffee
+++ core/app/backbone/views/sidebar_profile_view.coffee
@@ -8,17 +8,17 @@

   templateHelpers: =>
     plural_followers: @plural_followers()
-    following:  @following_count()
-    followers:  @followers_count()
+    following:  @following_count_object()
+    followers:  @followers_count_object()

   plural_followers: ->
-    @followers_count() isnt 1
+    @model.followers.followers_count() isnt 1

-  followers_count: ->
+  followers_count_object: ->
     count = @model.followers.followers_count()
     if count? then {count: count} else null

-  following_count: ->
+  following_count_object: ->
     count = @model.following.following_count()
     if count? then {count: count} else null


Fixed invalid variable reference.

Fixed invalid variable reference.

--- app/assets/javascripts/cartilage/views/list_view.js.coffee
+++ app/assets/javascripts/cartilage/views/list_view.js.coffee
@@ -54,7 +54,7 @@
     itemElement

   addModel: (model) =>
-    @renderModel(foo).appendTo ($ @el)
+    @renderModel(model).appendTo ($ @el)

   #
   # Selects the specified list item. Returns true if the item was selected or


Updated style of button.

Updated style of button.

--- app/assets/javascripts/product_categories.js.coffee
+++ app/assets/javascripts/product_categories.js.coffee
@@ -46,12 +46,12 @@
     return window.confirm confirm

   modal = $ """
-

-

-

-
#{message}

-

-
+

+

+

+
#{message}

+

+
           #{confirm_localization['cancel']}

@@ -69,7 +69,7 @@
   confirm_button
     .removeAttr('class')
     .removeAttr('data-confirm')
-    .addClass('button radius red inline confirm')
+    .addClass('button radius red full-width inline confirm')
     .html(confirm.ok || confirm_localization['ok'])

   if element.is('form') or element.is(':input')


fix coffee script syntax

fix coffee script syntax

--- js/inner/billing.coffee
+++ js/inner/billing.coffee
@@ -251,8 +251,11 @@
       success: (data) =>
         mixpanel.track('Paid')
         window._pq = window._pq or []
-        _pq.push ["track" "payer"]
-        __adroll.record_user({"adroll_segments": "payer"})
+        _pq.push [
+            "track"
+            "payer"
+        ]
+        __adroll.record_user adroll_segments: "payer"
         @loadPlanData(data)
         @loadInvoices()
         VM.org().subpage('containers')


Remove underscore

Remove underscore

--- buzzer.coffee
+++ buzzer.coffee
@@ -1,5 +1,4 @@
 gpio = require('gpio')
-_ = require('underscore')
 Events = require('events').EventEmitter

 module.exports = class Buzzer extends Events
@@ -24,7 +23,8 @@
       # Due to the asynchronous nature of exporting a header, you may not be able to
       # read or write to the header right away. Place your logic in this ready
       # function to guarantee everything will get fired properly
-      ready: _.bind( @, @onPinReady )
+      ready: =>
+        @onPinReady
     )

   onPinReady: ->


Fix wrong conditional order for user page editor

--- resources/assets/coffee/react/profile-page/user-page.coffee
+++ resources/assets/coffee/react/profile-page/user-page.coffee
@@ -52,20 +52,10 @@
       __html: @props.userPage.html

   render: =>
-    withEditButton = @props.withEdit
-
-    if withEditButton && @props.userPage.html == ''
-      withEditButton = false
-      page = @pageNew()
-    else if @props.userPage.editing
-      page = el ProfilePage.UserPageEditor, userPage: @props.userPage
-    else
-      page = @pageShow()
-
     el 'div', className: 'profile-extra',
       el ProfilePage.ExtraHeader, name: @props.name, withEdit: @props.withEdit

-      if withEditButton && !@props.userPage.editing
+      if !@props.userPage.editing && @props.withEdit && @props.userPage.html != ''
         el 'div', className: 'profile-extra__actions',
           el 'div', className: 'forum-post-actions',
             el 'a',
@@ -73,4 +63,9 @@
               href: '#'
               onClick: @editStart
               el 'i', className: 'fa fa-edit'
-      page
+      if @props.userPage.editing
+        el ProfilePage.UserPageEditor, userPage: @props.userPage
+      else if @props.withEdit && @props.userPage.html == ''
+        @pageNew()
+      else
+        @pageShow()


Removed left over debug code

Removed left over debug code

--- resources/assets/scripts/app/controllers/track-stats.coffee
+++ resources/assets/scripts/app/controllers/track-stats.coffee
@@ -25,8 +25,6 @@
         cumulativeArray = []

         statsLoaded = (stats) ->
-            console.log(stats)
-
             for key, value of stats.playStats
                 labelArray.push value.hour || value.days
                 dailyArray.push value.plays


Ctrl/cmd+click a level in campign editor to open level editor in new tab.

--- app/views/editor/campaign/CampaignEditorView.coffee
+++ app/views/editor/campaign/CampaignEditorView.coffee
@@ -201,8 +201,11 @@

   onCampaignLevelClicked: (levelOriginal) ->
     return unless levelTreema = @treema.childrenTreemas?.levels?.childrenTreemas?[levelOriginal]
+    if key.ctrl
+      url = "/editor/level/#{levelTreema.data.slug}"
+      window.open url, '_blank'
     levelTreema.select()
-#    levelTreema.open()
+    #levelTreema.open()

   onCampaignLevelDoubleClicked: (levelOriginal) ->
     @openCampaignLevelView @supermodel.getModelByOriginal Level, levelOriginal


Proper class jquery selector for `off`.

Proper class jquery selector for `off`.

--- app/assets/javascripts/lib/text_utility.js.coffee
+++ app/assets/javascripts/lib/text_utility.js.coffee
@@ -124,6 +124,6 @@
       gl.text._previousState = $thisTextarea.val()

   gl.text.removeListeners = () ->
-    $('js-md').off()
+    $('.js-md').off()

 ) window


fix a bug

fix a bug

--- app/assets/javascripts/social-share-button.coffee
+++ app/assets/javascripts/social-share-button.coffee
@@ -25,6 +25,7 @@
       when "weibo"
         SocialShareButton.openUrl("http://service.weibo.com/share/share.php?url=#{url}&type=3&pic=#{img}&title=#{title}&appkey=#{appkey}",popup)
       when "twitter"
+        via_str = ''
         via_str = "&via=#{via}" if via.length > 0
         SocialShareButton.openUrl("https://twitter.com/intent/tweet?url=#{url}&text=#{title}#{via_str}",popup)
       when "douban"


Add fallback for skydrive value in tabs

Add fallback for skydrive value in tabs

--- app/assets/javascripts/uploadcare/settings.coffee
+++ app/assets/javascripts/uploadcare/settings.coffee
@@ -242,6 +242,11 @@
     if settings.previewProxy and not settings.previewUrlCallback
       settings.previewUrlCallback = defaultPreviewUrlCallback

+    skydriveIndex = settings.tabs.indexOf('skydrive')
+
+    if skydriveIndex != -1
+      settings.tabs[skydriveIndex] = 'onedrive'
+
     settings


MainController: KodingAppsController doesn't need to wait for main app is ready

--- client/Main/maincontroller/maincontroller.coffee
+++ client/Main/maincontroller/maincontroller.coffee
@@ -57,6 +57,7 @@
     KD.registerSingleton 'dock',                      new DockController
     KD.registerSingleton 'mainView',             mv = new MainView domId : 'kdmaincontainer'
     KD.registerSingleton 'mainViewController',  mvc = new MainViewController view : mv
+    KD.registerSingleton 'kodingAppsController',      new KodingAppsController

     router.listen()
     @mainViewController = mvc
@@ -64,7 +65,6 @@

     @ready =>
       KD.registerSingleton 'widgetController',        new WidgetController
-      KD.registerSingleton 'kodingAppsController',    new KodingAppsController
       KD.registerSingleton 'onboardingController',    new OnboardingController
       KD.registerSingleton "socialapi",               new SocialApiController
       # KD.registerSingleton "kontrol",                 new Kontrol


MachineSettingsView: instead of adding and hiding buttons, do not add them initially

--- client/Main/providers/machinesettingsview.coffee
+++ client/Main/providers/machinesettingsview.coffee
@@ -269,20 +269,6 @@
         computeController.reinit @machine
         @destroy()

-    @buttonContainer.addSubView @resizeButton = new KDButtonView
-      style    : 'solid compact green resize hidden'
-      title    : 'Resize VM'
-      callback : =>
-        computeController.resize @machine, 10
-        @destroy()
-
-    @buttonContainer.addSubView @terminateButton = new KDButtonView
-      style    : 'solid compact red'
-      title    : 'Terminate VM'
-      callback : =>
-        computeController.destroy @machine
-        @destroy()
-
     @addSubView new KDCustomHTMLView
       cssClass : 'modal-arrow'
       position : top : 20
@@ -291,9 +277,18 @@

       @isPaidAccount = plan isnt 'free'

-      if plan in ['free', 'hobbyist']
-        @terminateButton.hide()
-
-        if plan is 'hobbyist' and @machine.jMachine.meta?.storage_size isnt 10
-          @resizeButton.show()
-
+      if plan is 'hobbyist' and @machine.jMachine.meta?.storage_size isnt 10
+        @buttonContainer.addSubView @resizeButton = new KDButtonView
+          style    : 'solid compact green resize'
+          title    : 'Resize VM'
+          callback : =>
+            computeController.resize @machine, 10
+            @destroy()
+
+      unless plan is 'free'
+        @buttonContainer.addSubView @terminateButton = new KDButtonView
+          style    : 'solid compact red'
+          title    : 'Terminate VM'
+          callback : =>
+            computeController.destroy @machine
+            @destroy()


Activity: creation of settings menu per activity item improved

--- client/Social/Activity/views/settingsview.coffee
+++ client/Social/Activity/views/settingsview.coffee
@@ -20,45 +20,58 @@

     activityController = KD.getSingleton('activityController')

-  settingsMenu:(post)->
-    account        = KD.whoami()
+
+  getOwnerMenu: (post) ->
+
+    account            = KD.whoami()
     activityController = KD.getSingleton("activityController")
-    menu = {}
-    if post.originId is account.getId()
-        menu['Edit Post'] =
-          callback: => @emit 'ActivityEditIsClicked'
-        menu['Delete Post'] =
-          callback: => @confirmDeletePost post
-
-    if KD.checkFlag("super-admin") or KD.hasAccess("delete posts")
-      if KD.checkFlag 'exempt', account
-        menu['Unmark User as Troll'] =
-            callback             : ->
-              activityController.emit "ActivityItemUnMarkUserAsTrollClicked", post
-      else
-        menu['Mark User as Troll'] =
-            callback           : ->
-              activityController.emit "ActivityItemMarkUserAsTrollClicked", post
-
-      menu['Delete Post'] =
-        callback : => @confirmDeletePost post
-
-      menu['Edit Post'] =
-        callback : => @emit 'ActivityEditIsClicked'
-
-      menu['Block User'] =
-        callback : ->
-          activityController.emit "ActivityItemBlockUserClicked", post.originId
-
-      menu['Add System Tag'] =
-        callback : => @selectSystemTag post
-
-      menu['Impersonate'] =
-        callback : ->
-          KD.remote.cacheable post.originType, post.originId, (err, owner) ->
-            return KD.showError err  if err
-            return KD.showError message: "Account not found"  unless owner
-            KD.impersonate owner.profile.nickname
+    menu               =
+      'Edit Post'      : callback : => @emit 'ActivityEditIsClicked'
+      'Delete Post'    : callback : => @confirmDeletePost post
+
+    return menu
+
+
+  getAdminMenu: (post) ->
+
+    account      = KD.whoami()
+    menu         = @getOwnerMenu()
+    addCallback  = (title, callback) -> menu[title] = {callback}
+
+    {activityController} = KD.singletons
+
+    if KD.checkFlag 'exempt', account
+      addCallback 'Unmark User as Troll', ->
+        activityController.emit "ActivityItemUnMarkUserAsTrollClicked", post
+    else
+      addCallback 'Mark User as Troll', ->
+        activityController.emit "ActivityItemMarkUserAsTrollClicked", post
+
+    addCallback 'Block User', ->
+      activityController.emit "ActivityItemBlockUserClicked", post.originId
+    addCallback 'Impersonate', ->
+      KD.remote.cacheable post.originType, post.originId, (err, owner) ->
+        return KD.showError err  if err
+        return KD.showError message: "Account not found"  unless owner
+        KD.impersonate owner.profile.nickname
+
+    addCallback 'Add System Tag', => @selectSystemTag post
+
+    return menu
+
+
+  settingsMenu: (post) ->
+
+    account = KD.whoami()
+    menu    = {}
+
+    activityController = KD.getSingleton("activityController")
+
+    menu = if post.originId is account.getId()
+    then @getOwnerMenu()
+    else if KD.checkFlag('super-admin') or KD.hasAccess('delete posts')
+    then @getAdminMenu()
+

     return menu


lod_and_effect: Add some additional mouse tips

  - Still having some trouble with d3-tip re location of tip

--- lod_and_effect/lod_and_effect.coffee
+++ lod_and_effect/lod_and_effect.coffee
@@ -240,6 +240,8 @@
             "red")
          .attr("stroke", "black")
          .attr("stroke-width", "2")
+         .on("mouseover", efftip)
+         .on("mouseout", -> d3.selectAll("#efftip").remove())

   # background rectangles for each chromosome, alternate color
@@ -369,6 +371,9 @@
                g = data.geno[marker][i]
                return "2" if g < 0
                "1")
+          .on("mouseover", indtip)
+          .on("mouseout", -> d3.selectAll("#indtip").remove())
+

   # function to revise phenotype vs genotype plot
   revPXG = (chr, marker) ->
@@ -438,12 +443,24 @@

   # Using https://github.com/Caged/d3-tip
   #   [slightly modified in https://github.com/kbroman/d3-tip]
-  tip = d3.svg.tip()
-          .orient("right")
-          .padding(3)
-          .text((z) -> z)
-          .attr("class", "d3-tip")
-          .attr("id", "d3tip")
+  martip = d3.svg.tip()
+             .orient("right")
+             .padding(3)
+             .text((z) -> z)
+             .attr("class", "d3-tip")
+             .attr("id", "martip")
+  indtip = d3.svg.tip()
+             .orient("right")
+             .padding(3)
+             .text((d,i) -> data.individuals[i])
+             .attr("class", "d3-tip")
+             .attr("id", "indtip")
+  efftip = d3.svg.tip()
+             .orient("right")
+             .padding(3)
+             .text((d) -> d3.format(".2f")(d))
+             .attr("class", "d3-tip")
+             .attr("id", "efftip")

   markerClick = []

@@ -470,10 +487,10 @@
           .attr("opacity", 0)
           .on("mouseover", (td) ->
                  d3.select(this).attr("opacity", 1) unless markerClick[td]
-                 tip.call(this,td))
+                 martip.call(this,td))
           .on "mouseout", (td) ->
                  d3.select(this).attr("opacity", markerClick[td])
-                 d3.selectAll("#d3tip").remove()
+                 d3.selectAll("#martip").remove()
           .on "click", (td) ->
                  pos = data.lod[chr].pos[data.markerindex[chr][td]]
                  title = "#{td} (chr #{chr}, #{onedig(pos)} cM)"


Allow calling goBack for multiple times

Allow calling goBack for multiple times

--- atom/browser/api/lib/navigation-controller.coffee
+++ atom/browser/api/lib/navigation-controller.coffee
@@ -10,15 +10,15 @@
     @pendingIndex = -1

     @webContents.on 'navigation-entry-commited', (event, url) =>
-      if @pendingIndex >= 0  # Go to index.
-        @currentIndex = @pendingIndex
-        @pendingIndex = -1
-        @history[@currentIndex] = url
-      else  # Normal navigation.
+      if @pendingIndex is -1  # Normal navigation.
         @history = @history.slice 0, @currentIndex + 1  # Clear history.
         if @history[@currentIndex] isnt url
           @currentIndex++
           @history.push url
+      else  # Go to index.
+        @currentIndex = @pendingIndex
+        @pendingIndex = -1
+        @history[@currentIndex] = url

   loadUrl: (url, options={}) ->
     @pendingIndex = -1
@@ -43,10 +43,10 @@
     @reload()

   canGoBack: ->
-    @currentIndex > 0
+    @getActiveIndex() > 0

   canGoForward: ->
-    @currentIndex < @history.length
+    @getActiveIndex() < @history.length - 1

   canGoToIndex: (index) ->
     index >=0 and index < @history.length
@@ -56,12 +56,12 @@

   goBack: ->
     return unless @canGoBack()
-    @pendingIndex = @currentIndex - 1
+    @pendingIndex = @getActiveIndex() - 1
     @webContents._loadUrl @history[@pendingIndex], {}

   goForward: ->
     return unless @canGoForward()
-    @pendingIndex = @currentIndex + 1
+    @pendingIndex = @getActiveIndex() + 1
     @webContents._loadUrl @history[@pendingIndex], {}

   goToIndex: (index) ->
@@ -73,4 +73,7 @@
     return unless @canGoToOffset offset
     @goToIndex @currentIndex + offset

+  getActiveIndex: ->
+    if @pendingIndex is -1 then @currentIndex else @pendingIndex
+
 module.exports = NavigationController


fix(build): fix lstatSync throwing

fix(build): fix lstatSync throwing

--- build/tasks/add-nylas-build-resources-task.coffee
+++ build/tasks/add-nylas-build-resources-task.coffee
@@ -18,9 +18,10 @@
       from = path.join(rootDir, 'packages', plugin)
       to = path.join(path.resolve('internal_packages'), plugin)

-      if fs.lstatSync(to)
-        grunt.log.writeln "Removing old symlink at #{to}"
-        fs.unlinkSync(to)
+      try
+        if fs.lstatSync(to)
+          grunt.log.writeln "Removing old symlink at #{to}"
+          fs.unlinkSync(to)

       grunt.log.writeln "Adding '#{plugin}' to internal_packages"
       fs.symlinkSync(from, to, 'dir')


remove underscore from pollSavingStatus

remove underscore from pollSavingStatus

the underscore module is not loading in time for some users on MacOS

--- services/web/public/coffee/ide/editor/controllers/SavingNotificationController.coffee
+++ services/web/public/coffee/ide/editor/controllers/SavingNotificationController.coffee
@@ -13,11 +13,14 @@
 		$scope.docSavingStatus = {}
 		pollSavedStatus = () ->
 			oldStatus = $scope.docSavingStatus
+			oldUnsavedCount = $scope.docSavingStatusCount
 			newStatus = {}
+			newUnsavedCount = 0

 			for doc_id, doc of Document.openDocs
 				saving = doc.pollSavedStatus()
 				if !saving
+					newUnsavedCount++
 					if oldStatus[doc_id]?
 						newStatus[doc_id] = oldStatus[doc_id]
 						newStatus[doc_id].unsavedSeconds += 1
@@ -28,9 +31,12 @@
 						}

 			# for performance, only update the display if the old or new
-			# statuses have any unsaved files
-			if _.size(newStatus) or _.size(oldStatus)
+			# counts of unsaved files are nonzeror.  If both old and new
+			# unsaved counts are zero then we know we are in a good state
+			# and don't need to do anything to the UI.
+			if newUnsavedCount or oldUnsavedCount
 				$scope.docSavingStatus = newStatus
+				$scope.docSavingStatusCount = newUnsavedCount
 				$scope.$apply()

 		warnAboutUnsavedChanges = () ->


Fix scanner delay that prevented video from appearing

--- app/src/controllers/common/dialogs/common_dialogs_qrcode_dialog_view_controller.coffee
+++ app/src/controllers/common/dialogs/common_dialogs_qrcode_dialog_view_controller.coffee
@@ -6,7 +6,7 @@

   onAfterRender: ->
     super
-    @_startScanner()
+    _.defer => @_startScanner()

   show: ->
     ledger.managers.permissions.request 'videoCapture', (granted) =>


Redirect to dashboard after 1000ms on ../opening

--- app/src/controllers/onboarding/device/onboarding_device_opening_view_controller.coffee
+++ app/src/controllers/onboarding/device/onboarding_device_opening_view_controller.coffee
@@ -3,6 +3,9 @@
   onAfterRender: ->
     super
     @view.spinner = ledger.spinners.createLargeSpinner(@select('div.greyed-container')[0])
+    setTimeout =>
+      ledger.app.router.go '/wallet/dashboard/index'
+    , 1000

   openSupport: ->
     window.open t 'application.support_url'


save crashreport in update function

save crashreport in update function

--- server/import-api.coffee
+++ server/import-api.coffee
@@ -27,7 +27,7 @@
         #copy fields from new crashreport replacing the old ones (does not deep copy!)
         _(crashreport_new).each (v,k) -> crashreport[k] = v

-        # TODO: ignore aggregate fields in validation
+        # TODO: ignore server-side added fields in validation
         #validate the crashreport
         #err = []
         #err = err.concat validate_crashreport_fields(crashreport)
@@ -43,12 +43,12 @@
             rcoreparser.parse_rich_core crashreport.files["rich-core"], (err, rcoredata) ->
                 return cb err if err?
                 crashreport["rich-core"] = rcoredata
-                cb null, crashreport
-
-        # save_crashreport crashreport, (err) ->
-        #     return cb err if err?
-        #     console.log "debug: Update done!" #debug
-        #     cb null, crashreport
+
+                # save to db
+                save_crashreport crashreport, (err) ->
+                    return cb err if err?
+                    console.log "debug: Updated crashreport id:#{crashreport.id}" #debug
+                    cb null, crashreport

 save_crashreport = (crashreport, cb) ->


fix missing bootstrap JS lib

fix missing bootstrap JS lib

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -1,5 +1,6 @@
 # = require jquery
 # = require jquery_ujs
+# = require bootstrap-sprockets
 # = require_self
 # = require_tree .


fixed a bug in saving / mercury:saved event -- fixes #119

--- vendor/assets/javascripts/mercury/page_editor.js.coffee
+++ vendor/assets/javascripts/mercury/page_editor.js.coffee
@@ -205,9 +205,9 @@
       dataType: @options.saveDataType || 'json'
       data: {content: data, _method: method}
       success: =>
-        callback() if callback
         Mercury.changes = false
         Mercury.trigger('saved')
+        callback() if typeof(callback) == 'function'
       error: =>
         Mercury.notify('Mercury was unable to save to the url: %s', url)
     }


Animate emoji when rendered.

Animate emoji when rendered.

--- app/assets/javascripts/awards_handler.coffee
+++ app/assets/javascripts/awards_handler.coffee
@@ -126,6 +126,7 @@
         counter.text parseInt(counter.text()) + 1
         $emojiBtn.addClass 'active'
         @addMeToUserList votesBlock, emoji
+        @animateEmoji $emojiBtn
     else
       votesBlock.removeClass 'hidden'
       @createEmoji votesBlock, emoji
@@ -242,13 +243,23 @@
       1
     </button>"

-    emoji_node = $(buttonHtml)
+    $emojiButton = $ buttonHtml
+    emoji_node   = $emojiButton
       .insertBefore votesBlock.find '.js-award-holder:not(.js-award-action-btn)'
       .find '.emoji-icon'
       .data 'emoji', emoji

+    @animateEmoji $emojiButton
     $('.award-control').tooltip()
     votesBlock.removeClass 'current'
+
+
+  animateEmoji: ($emoji) ->
+
+    className = 'pulse animated'
+
+    $emoji.addClass className
+    setTimeout (-> $emoji.removeClass className), 321

   createEmoji: (votesBlock, emoji) ->


Allow custom dataType on page save

Allow custom dataType on page save

This allows you to set the dataType that is expected to be returned by the server when saving a page.

This can be helpful when you want the server to return some custom javascript action instead of a json value.

You can read more about this at http://api.jquery.com/jQuery.ajax/#jQuery-ajax-settings

--- vendor/assets/javascripts/mercury/page_editor.js.coffee
+++ vendor/assets/javascripts/mercury/page_editor.js.coffee
@@ -2,6 +2,7 @@

   # options
   # saveStyle: 'form', or 'json' (defaults to json)
+  # saveDataType: 'xml', 'json', 'jsonp', 'script', 'text', 'html' (defaults to json)
   # saveMethod: 'POST', or 'PUT', create or update actions on save (defaults to POST)
   # visible: boolean, if the interface should start visible or not (defaults to true)
   constructor: (@saveUrl = null, @options = {}) ->
@@ -169,7 +170,7 @@
     method = 'PUT' if @options.saveMethod == 'PUT'
     jQuery.ajax url, {
       type: method || 'POST'
-      dataType: 'json'
+      dataType: @options.saveDataType || 'json'
       headers: @saveHeaders()
       data: {content: data, _method: method}
       success: =>


Move target line out of viewport if no target set

If no target is set (present_value() or future_value() equals null), no target
line is visible as it is moved out of the viewport. If the target is set to
0, the target line is displayed as expected. Closes #1452.

--- app/assets/javascripts/d3/stacked_bar.coffee
+++ app/assets/javascripts/d3/stacked_bar.coffee
@@ -156,9 +156,9 @@
         .transition()
         .attr 'y', (d) =>
           value = if d.get('target_line_position') is '1'
-            d.safe_present_value()
+            if d.safe_present_value() == 0 then 3*@series_height else d.safe_present_value()
           else
-            d.safe_future_value()
+            if d.safe_future_value() == 0 then 3*@series_height else d.safe_future_value()

           @series_height - @y(value)


Allow to pass non-asynchronous data to GitLabDropdown

--- app/assets/javascripts/gl_dropdown.js.coffee
+++ app/assets/javascripts/gl_dropdown.js.coffee
@@ -83,15 +83,19 @@
     search_fields = if @options.search then @options.search.fields else [];

     if @options.data
-      # Remote data
-      @remote = new GitLabDropdownRemote @options.data, {
-        dataType: @options.dataType,
-        beforeSend: @toggleLoading.bind(@)
-        success: (data) =>
-          @fullData = data
-
-          @parseData @fullData
-      }
+      # If data is an array
+      if _.isArray @options.data
+        @parseData @options.data
+      else
+        # Remote data
+        @remote = new GitLabDropdownRemote @options.data, {
+          dataType: @options.dataType,
+          beforeSend: @toggleLoading.bind(@)
+          success: (data) =>
+            @fullData = data
+
+            @parseData @fullData
+        }

     # Init filiterable
     if @options.filterable
@@ -204,7 +208,12 @@
     else
       selected = if @options.isSelected then @options.isSelected(data) else false
       url = if @options.url then @options.url(data) else "#"
-      text = if @options.text then @options.text(data) else ""
+
+      if @options.text?
+        text = @options.text(data)
+      else
+        text = data.text if data.text?
+
       cssClass = "";

       if selected


Reduced requested pcap buffer size to 1 MB

--- amazing-dash-button.coffee
+++ amazing-dash-button.coffee
@@ -72,7 +72,7 @@
         device = cap.findDevice()
       @base.debug "Sniffing for ARP requests on device", device

-      linkType = @capture.open device, 'arp', 10 * 1024 * 1024, @buffer
+      linkType = @capture.open device, 'arp', 1024 * 1024, @buffer
       try
         @capture.setMinBytes 0
       catch e


Include leaguedata to player when created

--- coffees/src/controllers/player_controller.coffee
+++ coffees/src/controllers/player_controller.coffee
@@ -14,8 +14,9 @@
       data =
         event: "newPlayer"
         player: player.toJSON()
-      request.app.get('redisClient').publish(player.LeagueId, JSON.stringify(data));
-      response.status(201).json(player)
+      request.app.get('redisClient').publish(player.LeagueId, JSON.stringify(data))
+      player.reload(include: [{ model: models.League }]).then (pl) ->
+        response.status(201).json(pl)
     ).catch((error) ->
       if error.name == "SequelizeValidationError" || error.name == "SequelizeUniqueConstraintError"
         response.status(400).json(error: error)


Fixed weird behavior of MailChimp API where it wouldn't update not-yet-confirmed subscribers' email addresses when changed and we'd end up sending the opt-in email a zillion times.

--- server/users/User.coffee
+++ server/users/User.coffee
@@ -123,6 +123,7 @@
   params.update_existing = true

   onSuccess = (data) ->
+    data.email = doc.get('email')  # Make sure that we don't spam opt-in emails even if MailChimp doesn't udpate the email it gets in this object until they have confirmed.
     doc.set('mailChimp', data)
     doc.updatedMailChimp = true
     doc.save()


send a strong etag for the output.pdf file, needed for byte ranges in pdf.js

--- services/clsi/app.coffee
+++ services/clsi/app.coffee
@@ -38,10 +38,17 @@
 staticServer = express.static Settings.path.compilesDir, setHeaders: (res, path, stat) ->
 	if Path.basename(path) == "output.pdf"
 		res.set("Content-Type", "application/pdf")
+		# Calculate an etag in the same way as nginx
+		# https://github.com/tj/send/issues/65
+		etag = (path, stat) ->
+			'"' + Math.ceil(+stat.mtime / 1000).toString(16) +
+			'-' + Number(stat.size).toString(16) + '"'
+		res.set("Etag", etag(path, stat))
 	else
 		# Force plain treatment of other file types to prevent hosting of HTTP/JS files
 		# that could be used in same-origin/XSS attacks.
 		res.set("Content-Type", "text/plain")
+
 app.get "/project/:project_id/output/*", (req, res, next) ->
 	req.url = "/#{req.params.project_id}/#{req.params[0]}"
 	staticServer(req, res, next)


run current file shortcut was changed to alt+shift+T

--- keymaps/atom-mocha.cson
+++ keymaps/atom-mocha.cson
@@ -11,4 +11,4 @@
   'ctrl-alt-t': 'atom-mocha:toggle'
   'ctrl-shift-r' : 'atom-mocha:rerunTests'
 'atom-text-editor':
-  'ctrl-shift-t' : 'atom-mocha:runTestFileFromEditor'
+  'alt-shift-t' : 'atom-mocha:runTestFileFromEditor'


graph: send cypher with params; fix bug where if err it called callback but did not return

--- workers/social/lib/social/models/graph/graph.coffee
+++ workers/social/lib/social/models/graph/graph.coffee
@@ -98,7 +98,7 @@
     @db.query query, {}, (err, results)=>
       if err
         console.log ">>> err", query, err
-        callback err
+        return callback err
       else if results.length is 0 then callback null, []
       else
         collectRelations = @attachReplies(options, callback)
@@ -172,7 +172,7 @@
     query = """
       START koding=node:koding("id:#{groupId}")
       MATCH koding-[:member]->members<-[:author]-content
-      WHERE content.`meta.createdAtEpoch` < #{startDate}
+      WHERE content.`meta.createdAtEpoch` < {startDate}
       """

     facets = @facets
@@ -224,7 +224,7 @@

       JCache.get query, (err, results)=>
         if err or not results
-          @db.query query, {}, (err, results)=>
+          @db.query query, {startDate}, (err, results)=>
             JCache.add query, results
             returnResults(err, results)
         else
@@ -234,7 +234,7 @@
     @db.query query, {}, (err, results) ->
       if err
         console.log ">>> err fetchRelateds error", query, err
-        callback err
+        return callback err
       resultData = []
       results.reverse()
       for result in results
@@ -279,16 +279,16 @@
       START kd=node:koding("id:#{groupId}")
       MATCH kd-[:member]->users<-[r:user]-apps
       WHERE apps.name="JApp"
-      and r.createdAtEpoch < #{startDate}
+      and r.createdAtEpoch < {startDate}
       return users, apps, r
       order by r.createdAtEpoch DESC
       limit 20
       """

-    @db.query query, {}, (err, results)=>
+    @db.query query, {startDate}, (err, results)=>
       if err
         console.log ">>> err fetchNewInstalledApps", query, err
-        callback err
+        return callback err
       @generateInstalledApps [], results, callback

   generateInstalledApps:(resultData, results, callback)->
@@ -353,16 +353,16 @@
     query = """
       start  koding=node:koding("id:#{groupId}")
       MATCH  koding-[r:member]->members
-      where  r.createdAtEpoch < #{startDate}
+      where  r.createdAtEpoch < {startDate}
       return members
       order by r.createdAtEpoch DESC
       limit 20
       """

-    @db.query query, {}, (err, results) ->
+    @db.query query, {startDate}, (err, results) ->
       if err
         console.log ">>> err fetchNewMembers", query, err
-        callback err, null
+        return callback err, null
       resultData = []
       for result in results
         data = result.members.data
@@ -378,12 +378,12 @@
       start koding=node:koding("id:#{groupId}")
       MATCH koding-[:member]->followees<-[r:follower]-follower
       where follower<-[:member]-koding
-      and r.createdAtEpoch < #{startDate}
+      and r.createdAtEpoch < {startDate}
       return r,followees, follower
       order by r.createdAtEpoch DESC
       limit 20
     """
-    @fetchFollows query, callback
+    @fetchFollows query, startDate, callback

   fetchTagFollows:(group, startDate, callback)->
     #followers
@@ -393,23 +393,21 @@
       MATCH koding-[:member]->followees<-[r:follower]-follower
       where follower.name="JTag"
       and follower.group="#{groupName}"
-      and r.createdAtEpoch < #{startDate}
+      and r.createdAtEpoch < {startDate}
       return r,followees, follower
       order by r.createdAtEpoch DESC
       limit 20
       """
-    @fetchFollows query, callback
-
-  fetchFollows:(query, callback)->
-    @db.query query, {}, (err, results)=>
+    @fetchFollows query, startDate, callback
+
+  fetchFollows:(query, startDate, callback)->
+    @db.query query, {startDate}, (err, results)=>
       if err
         console.log ">>> err fetchFollows", query, err
-        callback err
+        return callback err
       @generateFollows [], results, callback

-
   generateFollows:(resultData, results, callback)->
-
     if results? and results.length < 1 then return callback null, resultData
     result = results.shift()
     data = {}
@@ -454,7 +452,7 @@
     @db.query query, options, (err, results) ->
       if err
         console.log ">>> err", query, err
-        callback err
+        return callback err
       count = if results and results[0]['count'] then results[0]['count'] else 0
       callback null, count

@@ -586,18 +584,18 @@

   queryMembers:(query, options={}, callback)->
     @db.query query, options, (err, results) ->
-        if err
-          console.log ">>> err queryMembers", query, err
-          callback err
-        resultData = []
-        for result in results
-          data = result.members.data
-          id = data.id
-          name = data.name
-          obj =  {id : id, name : name }
-          resultData.push obj
-
-        callback err, resultData
+      if err
+        console.log ">>> err queryMembers", query, err
+        return callback err
+      resultData = []
+      for result in results
+        data = result.members.data
+        id = data.id
+        name = data.name
+        obj =  {id : id, name : name }
+        resultData.push obj
+
+      callback err, resultData

   ## NEWER IMPLEMENATION: Fetch ids from graph db, get items from document db.

@@ -629,7 +627,7 @@
     @db.query query, {}, (err, results)=>
       if err
         console.log ">>> err fetchItems", query, err
-        callback err
+        return callback err
       else
         tempRes = []
         collectContents = race (i, id, fin)=>


group: bugfix: context

group: bugfix: context

--- workers/social/lib/social/models/group/index.coffee
+++ workers/social/lib/social/models/group/index.coffee
@@ -1497,7 +1497,7 @@
   createVM: secure ({connection:{delegate}}, data, callback)->
     @checkVmType data, (err) =>
       return callback err  if err
-      @fetchOrCreateBundle (err, bundle)->
+      @fetchOrCreateBundle (err, bundle) =>
         return callback err  if err
         bundle.createVM delegate, this, data, callback


put back toggleFeature method

put back toggleFeature method

--- workers/social/lib/social/models/group/index.coffee
+++ workers/social/lib/social/models/group/index.coffee
@@ -212,6 +212,8 @@
         fetchPermissionSetOrDefault:
           (signature Function)
         fetchUserStatus:
+          (signature Object, Function)
+        toggleFeature:
           (signature Object, Function)
     schema          :
       title         :
@@ -830,6 +832,30 @@
         else policy.update $set: formData, callback

+  toggleFeature: permit 'grant permissions',
+    success:(client, options, callback)->
+      if not options.feature or not options.role or not options.operation
+        return callback {message:"request is not valid"}
+
+      @disabledFeatures = {}  unless @disabledFeatures
+      @disabledFeatures[options.role] =[]  unless @disabledFeatures[options.role]
+
+      if options.operation is "disable"
+        if options.feature not in @disabledFeatures?[options.role]
+          @disabledFeatures[options.role].push options.feature
+          return @update callback
+        else
+          return callback {message:"item is not in the list "}
+      else
+
+        if options.feature not in @disabledFeatures?[options.role]
+          return callback {message:"item is not in the list"}
+        else
+          ops = (feature for feature in @disabledFeatures?[options.role] when feature isnt options.feature)
+          @disabledFeatures[options.role] = ops
+          return @update callback
+
+
   canEditGroup: permit 'grant permissions',
     success: (client, callback)->
       callback null, yes