[Graph] change limit count to graph count

--- workers/social/lib/social/models/graph/queryregistry.coffee
+++ workers/social/lib/social/models/graph/queryregistry.coffee
@@ -1,166 +1,168 @@
+{Base} = require 'bongo'
+
 module.exports =

-    member      :
-      following : (orderByQuery)->
-        """
-          START group=node:koding(id={groupId})
-          MATCH group-[r:member]->members-[:follower]->currentUser
-          WHERE currentUser.id = {currentUserId}
-          RETURN members
-          #{orderByQuery}
-          SKIP {skipCount}
-          LIMIT {limitCount}
-        """
-      follower  : (orderByQuery)->
-        """
-          START group=node:koding(id={groupId})
-          MATCH group-[r:member]->members<-[:follower]-currentUser
-          WHERE currentUser.id = {currentUserId}
-          RETURN members
-          #{orderByQuery}
-          SKIP {skipCount}
-          LIMIT {limitCount}
-        """
-      list: (exemptClause, orderByQuery)->
-        """
-          START group=node:koding(id={groupId})
-          MATCH group-[r:member]->members
-          WHERE members.name="JAccount"
-          #{exemptClause}
-          RETURN members
-          #{orderByQuery}
-          SKIP {skipCount}
-          LIMIT {limitCount}
-        """
-      count: (exemptClause)->
-        """
+  member      :
+    following : (orderByQuery)->
+      """
         START group=node:koding(id={groupId})
-        MATCH group-[:member]->members
+        MATCH group-[r:member]->members-[:follower]->currentUser
+        WHERE currentUser.id = {currentUserId}
+        RETURN members
+        #{orderByQuery}
+        SKIP {skipCount}
+        LIMIT {limitCount}
+      """
+    follower  : (orderByQuery)->
+      """
+        START group=node:koding(id={groupId})
+        MATCH group-[r:member]->members<-[:follower]-currentUser
+        WHERE currentUser.id = {currentUserId}
+        RETURN members
+        #{orderByQuery}
+        SKIP {skipCount}
+        LIMIT {limitCount}
+      """
+    list: (exemptClause, orderByQuery)->
+      """
+        START group=node:koding(id={groupId})
+        MATCH group-[r:member]->members
         WHERE members.name="JAccount"
         #{exemptClause}
-        RETURN count(members) as count
-        """
+        RETURN members
+        #{orderByQuery}
+        SKIP {skipCount}
+        LIMIT {limitCount}
+      """
+    count: (exemptClause)->
+      """
+      START group=node:koding(id={groupId})
+      MATCH group-[:member]->members
+      WHERE members.name="JAccount"
+      #{exemptClause}
+      RETURN count(members) as count
+      """

-      search: (options)->
-        {seed, firstNameRegExp, lastNameRegexp, blacklistQuery, exemptClause} = options
-        """
-          START koding=node:koding(id={groupId})
-          MATCH koding-[r:member]->members
+    search: (options)->
+      {seed, firstNameRegExp, lastNameRegexp, blacklistQuery, exemptClause} = options
+      """
+        START koding=node:koding(id={groupId})
+        MATCH koding-[r:member]->members

-          WHERE  (
-            members.`profile.nickname` =~ '(?i)#{seed}'
-            and members.type = 'registered'
-            or members.`profile.firstName` =~ '(?i)#{firstNameRegExp}'
-            or members.`profile.lastName` =~ '(?i)#{lastNameRegexp}'
-          )
+        WHERE  (
+          members.`profile.nickname` =~ '(?i)#{seed}'
+          and members.type = 'registered'
+          or members.`profile.firstName` =~ '(?i)#{firstNameRegExp}'
+          or members.`profile.lastName` =~ '(?i)#{lastNameRegexp}'
+        )

-          #{blacklistQuery}
-          #{exemptClause}
+        #{blacklistQuery}
+        #{exemptClause}

-          RETURN members
-          ORDER BY members.`profile.firstName`
-          SKIP {skipCount}
-          LIMIT {limitCount}
-        """
-    bucket      :
-      newMembers :
-        """
-          START group=node:koding(id={groupId})
-          MATCH group-[r:member]->members
-          WHERE r.createdAtEpoch < {to}
-          RETURN members
-          ORDER BY r.createdAtEpoch DESC
-          LIMIT 50
-        """
-      newInstallations :
-        """
-          START group=node:koding(id={groupId})
-          MATCH group-[:member]->users<-[r:user]-apps
-          WHERE apps.name="JApp"
+        RETURN members
+        ORDER BY members.`profile.firstName`
+        SKIP {skipCount}
+        LIMIT {limitCount}
+      """
+  bucket      :
+    newMembers :
+      """
+        START group=node:koding(id={groupId})
+        MATCH group-[r:member]->members
+        WHERE r.createdAtEpoch < {to}
+        RETURN members
+        ORDER BY r.createdAtEpoch DESC
+        LIMIT {limitCount}
+      """
+    newInstallations :
+      """
+        START group=node:koding(id={groupId})
+        MATCH group-[:member]->users<-[r:user]-apps
+        WHERE apps.name="JApp"
+        AND r.createdAtEpoch < {to}
+        RETURN users, apps, r
+        ORDER BY r.createdAtEpoch DESC
+        LIMIT {limitCount}
+      """
+    newUserFollows :
+      """
+        START group=node:koding(id={groupId})
+        MATCH group-[:member]->followees<-[r:follower]-follower
+        WHERE follower<-[:member]-group
+        AND r.createdAtEpoch < {to}
+        RETURN r,followees, follower
+        ORDER BY r.createdAtEpoch DESC
+        LIMIT {limitCount}
+      """
+    newTagFollows :
+      """
+        START koding=node:koding(id={groupId})
+        MATCH koding-[:member]->followees<-[r:follower]-follower
+        WHERE follower.name="JTag"
+          AND follower.group = {groupName}
           AND r.createdAtEpoch < {to}
-          RETURN users, apps, r
-          ORDER BY r.createdAtEpoch DESC
-          LIMIT 50
-        """
-      newUserFollows :
-        """
-          START group=node:koding(id={groupId})
-          MATCH group-[:member]->followees<-[r:follower]-follower
-          WHERE follower<-[:member]-group
-          AND r.createdAtEpoch < {to}
-          RETURN r,followees, follower
-          ORDER BY r.createdAtEpoch DESC
-          LIMIT 50
-        """
-      newTagFollows :
-        """
-          START koding=node:koding(id={groupId})
-          MATCH koding-[:member]->followees<-[r:follower]-follower
-          WHERE follower.name="JTag"
-            AND follower.group = {groupName}
-            AND r.createdAtEpoch < {to}
-          RETURN r,followees, follower
-          ORDER BY r.createdAtEpoch DESC
-          LIMIT 50
-        """
-    activity    :
-      public :(facetQuery="",groupFilter="", exemptClause="")->
-        """
-          START group=node:koding(id={groupId})
-          MATCH group-[:member]->members<-[:author]-content
-          WHERE content.`meta.createdAtEpoch` < {to}
-          #{facetQuery}
-          #{groupFilter}
-          #{exemptClause}
-          RETURN content
-          ORDER BY content.`meta.createdAtEpoch` DESC
-          LIMIT {limitCount}
-        """
+        RETURN r,followees, follower
+        ORDER BY r.createdAtEpoch DESC
+        LIMIT {limitCount}
+      """
+  activity    :
+    public :(facetQuery="",groupFilter="", exemptClause="")->
+      """
+        START group=node:koding(id={groupId})
+        MATCH group-[:member]->members<-[:author]-content
+        WHERE content.`meta.createdAtEpoch` < {to}
+        #{facetQuery}
+        #{groupFilter}
+        #{exemptClause}
+        RETURN content
+        ORDER BY content.`meta.createdAtEpoch` DESC
+        LIMIT {limitCount}
+      """

-      following:(facet="", timeQuery="", exemptClause="")->
-        """
-          START member=node:koding(id={userId})
-          MATCH member<-[:follower]-members-[:author]-content
-          WHERE members.name="JAccount"
-          AND content.group = {groupName}
-          #{facet}
-          #{timeQuery}
-          #{exemptClause}
-          RETURN DISTINCT content
-          ORDER BY content.`meta.createdAtEpoch` DESC
-          LIMIT {limitCount}
-        """
+    following:(facet="", timeQuery="", exemptClause="")->
+      """
+        START member=node:koding(id={userId})
+        MATCH member<-[:follower]-members-[:author]-content
+        WHERE members.name="JAccount"
+        AND content.group = {groupName}
+        #{facet}
+        #{timeQuery}
+        #{exemptClause}
+        RETURN DISTINCT content
+        ORDER BY content.`meta.createdAtEpoch` DESC
+        LIMIT {limitCount}
+      """

-      profilePage: (options)->
-        """
-          START member=node:koding(id={userId})
-          MATCH member<-[:author]-content
-          WHERE content.originId = {userId}
-          #{options.facetQuery}
-          RETURN content
-          ORDER BY #{options.orderBy} DESC
-          SKIP {skipCount}
-          LIMIT {limitCount}
-        """
+    profilePage: (options)->
+      """
+        START member=node:koding(id={userId})
+        MATCH member<-[:author]-content
+        WHERE content.originId = {userId}
+        #{options.facetQuery}
+        RETURN content
+        ORDER BY #{options.orderBy} DESC
+        SKIP {skipCount}
+        LIMIT {limitCount}
+      """

-    invitation  :
-      list     :(status, timestampQuery="", searchQuery="")->
-        """
-          START group=node:koding(id={groupId})
-          MATCH group-[r:owner]->groupOwnedNodes
-          WHERE groupOwnedNodes.name = 'JInvitationRequest'
-          AND groupOwnedNodes.status IN #{status}
-          #{timestampQuery}
-          #{searchQuery}
-          RETURN groupOwnedNodes
-          ORDER BY groupOwnedNodes.`meta.createdAtEpoch`
-          LIMIT {limitCount}
-        """
-    aggregation :
-      relationshipCount:(relationshipName)->
-        """
-          START group=node:koding(id={groupId})
-          MATCH group-[:#{relationshipName}]->items
-          RETURN count(items) as count
-        """
+  invitation  :
+    list     :(status, timestampQuery="", searchQuery="")->
+      """
+        START group=node:koding(id={groupId})
+        MATCH group-[r:owner]->groupOwnedNodes
+        WHERE groupOwnedNodes.name = 'JInvitationRequest'
+        AND groupOwnedNodes.status IN #{status}
+        #{timestampQuery}
+        #{searchQuery}
+        RETURN groupOwnedNodes
+        ORDER BY groupOwnedNodes.`meta.createdAtEpoch`
+        LIMIT {limitCount}
+      """
+  aggregation :
+    relationshipCount:(relationshipName)->
+      """
+        START group=node:koding(id={groupId})
+        MATCH group-[:#{relationshipName}]->items
+        RETURN count(items) as count
+      """


Look for plugins in node_modules before trying globally

--- src/core/environment.coffee
+++ src/core/environment.coffee
@@ -110,31 +110,40 @@
     return groups

   loadPluginModule: (module, callback) ->
-    ### Load a plugin *module* and add it to the environment. ###
+    ### Load a plugin *module*. ###
+    loaded = false
     done = (error) ->
       if error?
-        if error.code is 'MODULE_NOT_FOUND'
+        if not loaded and error.code is 'MODULE_NOT_FOUND'
           error.message = "Can not find plugin '#{ module }'"
         else
           error.message = "Error loading plugin '#{ module }': #{ error.message }"
       callback error
     @logger.verbose "loading plugin: #{ module }"
-    try
-      # load plugin module
-      fn = require @resolveModulePath module
-    catch error
-      if error.code is 'MODULE_NOT_FOUND' and module[0] isnt '.'
-        # also try in env's node_modules
-        try
-          fn = require @resolveModulePath "./node_modules/#{ module }"
-        catch error
+    if module[0] isnt '.'
+      # global module, first look in node_modules
+      try
+        fn = require @resolveModulePath "./node_modules/#{ module }"
+      catch error
+        if error.code is 'MODULE_NOT_FOUND'
+          # not locally installed, check globally
+          try
+            fn = require @resolveModulePath module
+          catch error
+            done error
+            return
+        else
           done error
           return
-      else
+    else
+      try
+        fn = require @resolveModulePath module
+      catch error
         done error
         return
     try
       # module loaded, run it
+      loaded = true
       fn this, done
     catch error
       done error


Pricing: Remove pricing page from full size app list.

--- client/app/lib/mainviewcontroller.coffee
+++ client/app/lib/mainviewcontroller.coffee
@@ -87,7 +87,7 @@
     html     = global.document.documentElement
     mainView = @getView()

-    fullSizeApps    = [ 'Login', 'Pricing', 'Activity', 'Teams', 'Welcome', 'content-display' ]
+    fullSizeApps    = [ 'Login', 'Activity', 'Teams', 'Welcome', 'content-display' ]
     appsWithSidebar = [
       'Activity', 'Members', 'content-display', 'Apps', 'Dashboard', 'Account'
       'Environments', 'Bugs', 'Welcome'


FinderTreeController: When new workspace created append it to related machineBox.

--- client/ide/finder/idefindertreecontroller.coffee
+++ client/ide/finder/idefindertreecontroller.coffee
@@ -49,4 +49,9 @@
     eventObj     = this
     options      = { name, machineUId, rootPath, machineLabel, eventObj }

-    KD.getSingleton('mainView').activitySidebar.createNewWorkspace options+    IDE.helpers.createWorkspace options
+
+    @once 'WorkspaceCreated', (workspace) =>
+      { activitySidebar } = KD.singletons.mainView
+      machineBox = activitySidebar.getMachineBoxByMachineUId machineUId
+      machineBox.addWorkspace workspace


Use correct name for the description.

Use correct name for the description.

--- bokehjs/test/models/transforms/jitter_transform.coffee
+++ bokehjs/test/models/transforms/jitter_transform.coffee
@@ -4,7 +4,7 @@
 {Collections} = utils.require "base"
 Jitter = utils.require("models/transforms/jitter").Model

-describe "step_interpolator_transform module", ->
+describe "Jitter transform module", ->
   source = {start: 0, end: 10}
   target = {start: 20, end: 80}


conn: watch for long intervals btwn pongs, throw disconnected

--- client/landing/app/MainApp/monitor_status.coffee
+++ client/landing/app/MainApp/monitor_status.coffee
@@ -193,6 +193,7 @@

   brokerInterval  = null
   failureCallback = null
+  lastPong        = null

   # use broker ping to determine internet connection
   pingBrokerOnInterval = ->
@@ -200,14 +201,26 @@
       clearTimeout failureCallback
       failureCallback = null

+      brokerPong = ->
+        # account for people disconnecting at night, then reconnecting in the
+        # morning; if reconnection happens before failureCallback is trigged,
+        # we won't know that disconnection has happened.
+        if lastPong && (Date.now() - lastPong) > 50*1000
+          log "lastPong greater than 50 secs, disconnecting"
+          status = KD.singletons.status
+          status.disconnect({reason:"internetDown"})
+
+        clearTimeout failureCallback
+        failureCallback = null
+        lastPong = Date.now()
+
       failureCallback = setTimeout ->
         log 'broker ping failed, running troubleshoot', failureCallback
         KD.troubleshoot(false)
-      , 2000
-
-      KD.remote.ping ->
-        clearTimeout failureCallback
-        failureCallback = null
+      , 3000
+
+      KD.remote.ping -> brokerPong()
+
     , 5000

   KD.remote.on 'connected', ->


fix required prototype bug

fix required prototype bug

--- BackofficeBundle/Resources/public/coffee/addPrototype.coffee
+++ BackofficeBundle/Resources/public/coffee/addPrototype.coffee
@@ -38,7 +38,7 @@
   return

 PO.formPrototype:: =
-  toogleAddButton: ->
+   toogleAddButton: ->
     if @limit is `undefined` or @index < @limit
       @createAddButton()
     else


just adding a warning to the comments

just adding a warning to the comments

--- web/scripts/doppio-api.coffee
+++ web/scripts/doppio-api.coffee
@@ -11,6 +11,8 @@
         I know Lawrence is working on a more general api, but I can't find it right now
         so I am just hacking this together to see if I can
         get it to work.
+    IMPORTANT:
+        WORK IN PROGRESS DOES NOT WORK AT THE MOMENT DO NOT RUN
     ###
     constructor: () ->
         # This bit assumes document is ready:


Added examples for tools.

Added examples for tools.

--- app/assets/javascripts/app/controllers/_default_navbar.js.coffee
+++ app/assets/javascripts/app/controllers/_default_navbar.js.coffee
@@ -16,3 +16,5 @@
 App.Config.set( 'New', { prio: 20000, parent: '', name: 'New', translate: true, target: '#new', class: 'add' }, 'NavBarRight' )

 App.Config.set( 'Misc', { prio: 90000, parent: '', name: 'Tools', translate: true, target: '#tools', child: true, class: 'tools' }, 'NavBar' )
+#App.Config.set( 'Misc1', { prio: 1600, parent: '#tools', name: 'Test 1', target: '#test1', role: [ 'Admin' ] }, 'NavBar' )
+#App.Config.set( 'Misc2', { prio: 1700, parent: '#tools', name: 'Test 2', target: '#test2', role: [ 'Admin' ] }, 'NavBar' )


Add args for PreviewTab constructor and change few methods to public

--- app/assets/javascripts/uploadcare/widget/tabs/preview-tab.coffee
+++ app/assets/javascripts/uploadcare/widget/tabs/preview-tab.coffee
@@ -11,7 +11,7 @@
 uploadcare.namespace 'widget.tabs', (ns) ->
   class ns.PreviewTab extends ns.BasePreviewTab

-    constructor: ->
+    constructor: (@container, @tabButton, @dialogApi, @settings, @name) ->
       super

       $.each @dialogApi.fileColl.get(), (i, file) =>
@@ -132,7 +132,7 @@
     # unknown
     # image
     # regular
-    __setState: (state, data) ->
+    __setState: (state, data) =>
       @__state = state
       data = data || {}

@@ -151,7 +151,7 @@
       if state is 'error'
         @container.addClass('uploadcare--preview_status_error-' + data.error)

-    initImage: (imgSize, cdnModifiers) ->
+    initImage: (imgSize, cdnModifiers) =>
       img = @container.find('.uploadcare--preview__image')
       done = @container.find('.uploadcare--preview__done')

@@ -188,7 +188,7 @@
           utils.defer(startCrop)

-    populateCropSizes: ->
+    populateCropSizes: =>
       if @settings.crop.length <= 1
         return


Updating

Updating

--- config.cson
+++ config.cson
@@ -73,7 +73,6 @@
     style: {}
   "tool-bar":
     position: "Left"
-    visible: false
   "tree-view":
     autoReveal: true
     hideVcsIgnoredFiles: false


Add methods to find or retrieve all students

--- src/flux/performance.coffee
+++ src/flux/performance.coffee
@@ -1,14 +1,25 @@
 {CrudConfig, makeSimpleStore, extendConfig} = require './helpers'
 _ = require 'underscore'

+allStudents = (performances) ->
+  _.chain(performances)
+    .pluck('students')
+    .flatten(true)
+    .value()
+
 PerformanceConfig = {
   exports:
+
+    getStudent: (courseId, roleId) ->
+      students = allStudents @_get(courseId)
+      # TODO remove parseInt when BE fixes role to be string
+      _.findWhere(allStudents(@_get(courseId)), role: parseInt(roleId))
+
+    getAllStudents: (courseId) ->
+      allStudents @_get(courseId)
+
     getStudentOfTask: (courseId, taskId) ->
-      performances = @_get(courseId)
-      students = _.chain(performances)
-        .pluck('students')
-        .flatten(true)
-        .value()
+      students = allStudents @_get(courseId)

       # TODO remove when BE fixed for ids to be strings instead of numbers
       taskId = parseInt(taskId)


added virgo level

added virgo level

--- src/game/states/game.coffee
+++ src/game/states/game.coffee
@@ -27,8 +27,8 @@
     ]
   },
   {
-    name: 'The Plough',
-    numStars: 9,
+    name: 'Bootes the Herdsman',
+    numStars: 8,
     startingStar: 0,
     starsArray: [
         {x: 381, y: 409},
@@ -37,9 +37,24 @@
         {x: 101, y: 31},
         {x: 222, y: 52},
         {x: 259, y: 210},
-        {x: 288, y: 544},
-        {x: 481, y: 397},
+        {x: 288, y: 544},
         {x: 519, y: 440},
+    ]
+  },
+  {
+    name: 'Virgo',
+    numStars: 9,
+    startingStar: 0,
+    starsArray: [
+        {x: 76, y: 222},
+        {x: 204, y: 266},
+        {x: 321, y: 372},
+        {x: 252, y: 485},
+        {x: 456, y: 287},
+        {x: 391, y: 188},
+        {x: 357, y: 30},
+        {x: 561, y: 271},
+        {x: 648, y: 230},
     ]
   },

@@ -67,11 +82,12 @@
     y = @level.starsArray[@level.startingStar].y

     #add Sprites
-    @game.add.sprite 0, 0, 'background'
+    @game.add.sprite 0, 0, 'background'
     #Plot empty stars
     @constellation = []
     @levelcomplete = false
     @drawConstellation(@level)
+    @add.text(560, 595, @level.name, { font: "18px Arial", fill: "#FFFF00", align: "center" })
     @player = @add.sprite x, y, 'player'
     @player.scale.set 0.5,0.5

@@ -101,10 +117,9 @@
         #if Player on star
              @game.add.tween(star).to({alpha:1},200,Phaser.Easing.Quintic.Out,true)
      	 else
-       #if Player not on star
-               @game.Tween.removeAllTweens();
+       #if Player not on star
      	       @game.add.tween(star).to({alpha:0.1},15000,Phaser.Easing.Quintic.Out,true)
-
+               #@game.Tween.removeAllTweens();
      	 if won
      	    if star.alpha < 0.35
      	       won = false


stop drawing control points for empty spreads

--- src/View/Canvas.coffee
+++ src/View/Canvas.coffee
@@ -32,8 +32,8 @@
         className: R.cx {
           LayoutMode: true
           FullScreen: layout.fullScreen
-          "icon-fullscreen": !layout.fullScreen
-          "icon-edit": layout.fullScreen
+          "icon-fullscreen": !layout.fullScreen
+          "icon-edit": layout.fullScreen
         }
         onClick: @_toggleLayout
       }
@@ -150,6 +150,7 @@
     return [] unless selectedParticularElement

     matrix = selectedParticularElement.accumulatedMatrix()
+    return [] unless matrix
     matrix = @_viewMatrix().compose(matrix)

     controlPoints = selectedParticularElement.element.controlPoints()


Add selectOne() / selectMany() to TableGateway

--- src/access/table-gateway.coffee
+++ src/access/table-gateway.coffee
@@ -26,13 +26,19 @@
         ar._load(@, data)
         return ar

-    findOne: () -> @doOne(@_find, arguments, 'find')
+    selectOne: (predicate, cb) -> @doOne(@_select, arguments, "select")
+    selectMany: (predicate, cb) -> @_query(predicate, "allRows", cb)
+    _select: (predicate, cb) -> @_query(predicate, "oneRow", cb)
+
+    findOne: () -> @doOne(@_find, arguments, "find")
+    findMany: (predicate, cb) -> @_query(predicate, "allObjects", cb)
+    _find: (predicate, cb) -> @_query(predicate, "oneObject", cb)
+
+    _query: (predicate, dbFunction, cb) ->
+        q = @newQuery().where(predicate)
+        return @db.bindOrCall(q, dbFunction, cb)

     deleteOne: () -> @doOne(@_delete, arguments, 'delete')
-
-    _find: (predicate, cb) ->
-        q = @newQuery().where(predicate)
-        return @db.bindOrCall(q, 'oneObject', cb)

     _delete: (predicate, cb) ->
         s = sql.delete(@sqlAlias(), predicate)
@@ -140,10 +146,6 @@
         s = sql.update(@sqlAlias(), values, predicate)
         return @db.bindOrCall(s, 'noData', cb)

-    findMany: (predicate, cb) ->
-        q = @newQuery().where(predicate)
-        return @db.bindOrCall(q, 'allObjects', cb)
-
     deleteMany: (predicate, cb) ->
         s = sql.delete(@sqlAlias()).where(predicate)
         return @db.bindOrCall(s, 'noData', cb)


Return the correct offset

Return the correct offset

--- src/adaptorMethods/where.coffee
+++ src/adaptorMethods/where.coffee
@@ -171,7 +171,7 @@
         _resultObject =
           className: self.className
           total: resultObject.totalResults
-          offset: start
+          offset: args.offset
           facets: resultObject.facetResults
           items: resultItems


debug coffee in core.config

debug coffee in core.config

--- src/app/core/core.config.coffee
+++ src/app/core/core.config.coffee
@@ -36,7 +36,7 @@
       return

-  return # appRun
+    return # appRun

 ionicConfig = ($ionicConfigProvider)->
   $ionicConfigProvider.backButton


using steroids events to track when the jsCore Bridge is ready.

--- src/bridges/JSCoreBridge.coffee
+++ src/bridges/JSCoreBridge.coffee
@@ -1,21 +1,27 @@
 # Communication bridge that utilizes an Object-C class exposed to the webview itself
 class JSCoreBridge extends Bridge
   constructor: ()->
-    #window.AG_SCREEN_ID = window.__JSCoreBridgeImpl.getAGScreenId()
-    #window.AG_LAYER_ID = window.__JSCoreBridgeImpl.getAGLayerId()
-    #window.AG_VIEW_ID = window.__JSCoreBridgeImpl.getAGViewId()
-
     return true

   @isUsable: ()->
     # __JSCoreAPIBridge is a Objective-C class (JSCoreBridge.m) exposed to the webview
-    console.log "JSCoreBridge.isUsable: #{typeof window.__JSCoreBridgeImpl}"
-    return typeof window.__JSCoreBridgeImpl != 'undefined'
+    return true

   sendMessageToNative:(message)->
-    window.__JSCoreBridgeImpl.send message
+    # Ensure websocket is open before sending anything
+    if window.__JSCoreBridgeImpl?
+      window.__JSCoreBridgeImpl.send message
+    else
+      window.steroids.on "jsCoreBridgeUsable", ()=>
+        window.__JSCoreBridgeImpl.send message

   parseMessage: (message={})->
     #JSON.stringify(message)
     #no need to turn into string for iOS
     message
+
+  message_handler: (msg)=>
+    # iOS parameters come as objects and not as Strings
+    if msg?.callback?
+      if @callbacks[msg.callback]?
+        @callbacks[msg.callback].call(msg.parameters, msg.parameters)


Fix bug when editing title after undoing title change [#145244601]

A recent bug-fix made the <input> DOM element persistent. This also made its contents persistent, which allowed the incorrect contents to be retrieved because the <input> element is uncontrolled. But since we have an onChange handler, we simply make the <input> element controlled at this point.

--- src/code/views/node-view.coffee
+++ src/code/views/node-view.coffee
@@ -81,7 +81,7 @@
       className: className
       onKeyUp: if canDeleteWhenEmpty then @detectDeleteWhenEmpty else null
       onChange: @updateTitle
-      defaultValue: displayTitle
+      value: displayTitle
       maxLength: @maxTitleLength()
       placeholder: @titlePlaceholder()
       onBlur: =>


Add missing Backbone import in core

Add missing Backbone import in core

--- src/coffee/cilantro/core.coffee
+++ src/coffee/cilantro/core.coffee
@@ -1,5 +1,6 @@
 define [
     'underscore'
+    'backbone'
     'promiser'
     './logger'
     './config'
@@ -12,7 +13,7 @@
     # Note, these are applied in place.
     'plugins/js'
     'plugins/jquery-ajax-queue'
-], (_, promiser, loglevel, config, channels, utils, changelog) ->
+], (_, Backbone, promiser, loglevel, config, channels, utils, changelog) ->

     c =
         # Version of cilantro


Bugfix Employer

Bugfix Employer

--- src/coffee/game/employer.coffee
+++ src/coffee/game/employer.coffee
@@ -58,7 +58,7 @@
       with.
       '''
       choices: [
-        choice 'employer1Rumor',
+        choice 'employer1CantripUp',
           'Buy a round of drinks'
           'Being liberal with Cantrip-Up always brings people running.'
           visible:
@@ -82,7 +82,7 @@
             progress: reqs.gte 7
       ]

-    storylet 'employer1Rumor',
+    storylet 'employer1CantripUp',
       'Buy a round of drinks'
       '''
       You meet many interesting people working the crowd. Some seem like they have potential. Others
@@ -124,7 +124,7 @@
       Brown's disappearance puts us at rather a disadvantage, so we couldn't possibly have been
       involved. It's been a great burden on us, until you came along."

-      As consolation, he pressed a small gemstone on you. "We have dozens, and I think
+      As consolation, he presses a small gemstone on you. "We have dozens, and I think
       someone in your line of work will find it eminently useful."
       '''
       consequences:


FIX #40 (entry form buttons layout)

FIX #40 (entry form buttons layout)

--- src/components/EntryForm.coffee
+++ src/components/EntryForm.coffee
@@ -112,12 +112,12 @@
             span className:"text","Ort"
             span className:"desc","(Klick auf Karte)"
           div className: "pure-g",
-            div className: "pure-u-1 pure-u-md-1-3",
+            div className: "pure-u-1 pure-u-md-1-2",
               label null, "Latitude"
               input fieldProps.lat
               lat.error and lat.touched and div className: "err", lat.error

-            div className: "pure-u-1 pure-u-md-1-3",
+            div className: "pure-u-1 pure-u-md-1-2",
               label null, "Longitude"
               input fieldProps.lng
               lng.error and lng.touched and div className: "err", lng.error
@@ -125,14 +125,14 @@
         fieldset null,
           legend null, ""
           div className: "pure-g",
-            div className: "pure-u-1 pure-u-md-1-3",
+            div className: "pure-u-1 pure-u-md-1-2",
               button
                 className: "pure-u-23-24 pure-button pure-button-primary"
                 type: "submit",
                   i className: "fa fa-floppy-o"
                   "speichern"

-            div className: "pure-u-1 pure-u-md-1-3",
+            div className: "pure-u-1 pure-u-md-1-2",
               button
                 onClick: (ev) =>
                   ev.preventDefault()


spreadsheet highlight/select color is orange now. standardizing color wip

--- web/volume/spreadsheet.coffee
+++ web/volume/spreadsheet.coffee
@@ -1064,7 +1064,7 @@
           if !info.t
             for c in info.cell.classList when c.startsWith('ss-')
               styles.set('.' + c + '{background-color:' +
-                (if c.includes('_', 4) then 'rgba(226,217,0,0.6)' else 'rgba(242,238,100,0.4)') +
+                (if c.includes('_', 4) then 'rgba(243,180,140,0.7)' else 'rgba(243,180,140,0.4)') +
                 ';\n text-}')
               info.cell.classList.add('selected')


Tabs order; minor style changes.

Tabs order; minor style changes.

--- background_scripts/completion.coffee
+++ background_scripts/completion.coffee
@@ -259,7 +259,7 @@
   # Suggestions from the Domain completer have the maximum relevancy. They should be shown first in the list.
   computeRelevancy: -> 1

-# TabRecency associates a timestamp with each tab id.
+# TabRecency associates a logical timestamp with each tab id.
 class TabRecency
   constructor: ->
     @timestamp = 1
@@ -297,10 +297,10 @@
       onComplete(suggestions)

   computeRelevancy: (suggestion) ->
-    if 0 < suggestion.queryTerms.length
+    if suggestion.queryTerms.length
       RankingUtils.wordRelevancy(suggestion.queryTerms, suggestion.url, suggestion.title)
     else
-      tabRecency.recencyScore suggestion.tabId
+      tabRecency.recencyScore(suggestion.tabId)

 # A completer which will return your search engines
 class SearchEngineCompleter


flv: Make warning message more specific

flv: Make warning message more specific

--- flv.coffee
+++ flv.coffee
@@ -143,7 +143,7 @@
           info.ascInfo = aac.readAudioSpecificConfig bits
           info.audioSpecificConfig = bits.marked_bytes()
         else
-          logger.warn "warn: flv:parseAudio(): AAC sequence header is empty"
+          logger.warn "flv:parseAudio(): warn: AAC sequence header does not contain AudioSpecificConfig"
       when api.AAC_PACKET_TYPE_RAW
         info.rawDataBlock = bits.remaining_buffer()
       else


Each folder should have its own contents collection

--- scripts/models/content/folder.coffee
+++ scripts/models/content/folder.coffee
@@ -9,6 +9,8 @@
     defaults:
       mediaType: 'application/vnd.org.cnx.folder'
       title: 'Untitled Folder'
-      contents: new Backbone.Collection()
+
+    initialize: () ->
+      @set('contents', new Backbone.Collection())

     #accepts: -> [ BaseBook::mediaType, BaseContent::mediaType ]


fix spotlight error

fix spotlight error

--- server/publications/spotlight.coffee
+++ server/publications/spotlight.coffee
@@ -1,5 +1,5 @@
 Meteor.publish 'spotlight', (selector, options, collName) ->
-	unless this.userId
+	if not this.userId? or not selector?.name?.$regex?
 		return this.ready()

 	console.log '[publish] spotlight -> '.green, 'selector:', selector, 'options:', options, 'collName:', collName


Whitespace

Whitespace

--- shepherd.coffee
+++ shepherd.coffee
@@ -1,7 +1,7 @@
 class Sheep
   # This is not actually used for anything
   # Just wanted to name a class Sheep
-
+
 ATTACHMENT =
   'top': 'top center'
   'left': 'middle right'


Onboarding: There is no need to escape path because of eval.

--- client/Main/onboarding/onboardingviewcontroller.coffee
+++ client/Main/onboarding/onboardingviewcontroller.coffee
@@ -17,7 +17,7 @@
     @show item

   show: (item) ->
-    path     = @escapePath item.partial.path
+    path     = item.path
     appName  = @app.getOptions().name
     itemName = item.name
     hasNext  = @items.indexOf(item) isnt @items.length - 1
@@ -34,13 +34,3 @@
     catch e
       details = { appName: appName, itemName: itemName, error: e }
       console.warn "Path parse error for onboarding item", details
-
-  escapePath: (path) ->
-    escaped  = path.replace(/\[/g, ".").replace(/\]/g, ".")
-    splitted = escaped.split "."
-    result   = splitted.first
-
-    for item, index in splitted when item
-      result += """["#{item}"]"""  if index > 0
-
-    return result


AdminModal: Save defaultChannels as string array.

--- client/admin/lib/views/groupgeneralsettingsview.coffee
+++ client/admin/lib/views/groupgeneralsettingsview.coffee
@@ -166,14 +166,26 @@
       title        : 'DELETE TEAM'

+  getChannelsArray: ->
+
+    return @generalSettingsForm.getFormData().channels # get input value
+      .split ','                                       # split from comma
+      .map    (i) -> return i.trim()                   # trim spaces
+      .filter (i) -> return i                          # filter empty values
+
+
   update: ->

     formData     = @generalSettingsForm.getFormData()
     jGroup       = @getData()
+    newChannels  = @getChannelsArray()
     dataToUpdate = {}

-    if formData.title isnt jGroup.title
+    unless formData.title is jGroup.title
       dataToUpdate.title = formData.title
+
+    unless _.isEqual newChannels, jGroup.defaultChannels
+      dataToUpdate.defaultChannels = newChannels

     return if _.isEmpty dataToUpdate


fix for app compile recursive symlink bug

--- client/landing/app/MainApp/kodingappscontroller.coffee
+++ client/landing/app/MainApp/kodingappscontroller.coffee
@@ -357,7 +357,7 @@
       @kiteController.run "kdc #{appPath}", (err, response)=>
         if not err
           nickname    = KD.nick()
-          publishPath = "/home/#{nickname}/Sites/.applications"
+          publishPath = "/home/#{nickname}/Web/.applications"
           @kiteController.run
             kiteName    : "os"
             method      : "fs.createDirectory"
@@ -366,7 +366,8 @@
               recursive : yes
           , (err, response)=>
             loader.notificationSetTitle "Publishing app static files..."
-            @kiteController.run "ln -s #{appPath} #{publishPath}/#{KD.utils.slugify name}", (err, response)=>
+            linkFile = "#{publishPath}/#{KD.utils.slugify name}"
+            @kiteController.run "rm #{linkFile}; ln -s #{appPath} #{linkFile}", (err, response)=>
               loader.notificationSetTitle "Fetching compiled app..."
               @fetchCompiledAppSource app, (err, res)=>
                 if not err


Emit AppsRefresh signal with fresh apps in refreshApps method of appscontroller

--- client/landing/app/MainApp/kodingappscontroller.coffee
+++ client/landing/app/MainApp/kodingappscontroller.coffee
@@ -159,7 +159,12 @@

     @constructor.manifests = {}
     KDApps = {}
-    @fetchAppsFromFs callback
+    @fetchAppsFromFs (err, apps)=>
+      if not err
+        @emit "AppsRefreshed", apps
+        callback? err, apps
+      else
+        callback err

   putAppsToAppStorage:(apps)->


landing: minor: removed comments

landing: minor: removed comments

--- client/landing/site.landing/coffee/login/routes.coffee
+++ client/landing/site.landing/coffee/login/routes.coffee
@@ -25,66 +25,6 @@
       app.getView().setCustomDataToForm('reset', {recoveryToken:token})
       app.getView().animateToForm('reset')

-  # handleVerifyRoute = ({params:{token}})->
-  #   router = KD.getSingleton 'router'
-  #   return router.handleRoute "/Login"  unless token
-  #   KD.singleton('appManager').open 'Login', (app) ->
-  #     KD.remote.api.JPasswordRecovery.validate token, (err, isValid)=>
-  #       return KD.notify_ err.message  if err
-  #       if isValid
-  #         KD.notify_ "Thanks for confirming your email address"
-  #         router.handleRoute "/Login"
-  #       else
-  #         KD.notify_ "Token is not valid"
-  #         router.handleRoute "/Login"
-
-  # handleRedeemRoute = ({params:{name, token}})->
-  #   token = decodeURIComponent token
-  #   KD.remote.api.JInvitation.byCode token, (err, invite)->
-  #     return callback err if err
-  #     if KD.isLoggedIn()
-  #       KD.remote.cacheable invite.group, (err, [group])->
-  #         group.redeemInvitation token, (err)->
-  #           if err
-  #             KD.notify_ err.message or err
-  #             return window.location.href = "/"
-
-  #           new KDNotificationView
-  #             title : 'Success!'
-  #             type  : 'tray'
-
-  #           KD.getSingleton('router').handleRoute "/#{group.slug}"
-  #           KD.getSingleton('mainController').accountChanged KD.whoami()
-  #     else
-  #       if err or !invite? or invite.status not in ['active','sent']
-  #         KD.singleton('appManager').open 'Login', (app) ->
-  #           new KDNotificationView
-  #             title: 'Invalid invitation code!'
-  #           app.getView().animateToForm 'login'
-  #       else
-  #         KD.singleton('appManager').open 'Login', (app) ->
-  #           app.getView().animateToForm 'login'
-  #           app.headBannerShowInvitation invite
-
-
-  #  handleFailureOfRestriction =->
-  #    KD.utils.defer -> new KDNotificationView title: "Login restricted"
-
-  #  handleRestriction = (handler) ->
-  #    ({params: {token : token }})->
-  #      for url in [
-  #        'http:\/\/localhost'
-  #        'https:\/\/koding.com'
-  #      ] when 0 is window.location.href.indexOf url
-  #        return do handler()
-
-  #      return handleFailureOfRestriction()  unless token
-
-  #      KD.remote.api.JInvitation.byCodeForBeta token, (err, invite)->
-  #        if err or !invite?
-  #          return handleFailureOfRestriction()
-
-  #        do handler()

   KD.registerRoutes 'Login',


Remove size restriction for the eager visualizer

--- demos/visualizer-eager/visualizer.coffee
+++ demos/visualizer-eager/visualizer.coffee
@@ -21,8 +21,7 @@
 run = (editors, compiler) ->
 	saveProgram editors

-	size = { width: 16, height: 10 }
-	playfield = new bef.Playfield editors.source.getValue(), size
+	playfield = new bef.Playfield editors.source.getValue()

 	runtime = new bef.EagerRuntime()
 	runtime.execute(


needed dotslash in front of json

needed dotslash in front of json

--- src/businesscat.coffee
+++ src/businesscat.coffee
@@ -20,7 +20,7 @@
   robot.hear regex, (msg) ->
     msg.send msg.random images

-images = require 'images.json'
+images = require './images.json'

 jargon = [
   'action items',


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/camera_fade.coffee
+++ src/camera_fade.coffee
@@ -65,5 +65,7 @@
   @param {Color} [color="transparent"] The color to fade to
   ###
   fadeOut: (duration = 30, color = 'transparent') ->
+    {alpha, color, duration} = Object.reverseMerge(options, fadeInDefaults)
+
     configureFade(duration, color, 1)


kill shadow

kill shadow

--- src/Ball.coffee
+++ src/Ball.coffee
@@ -51,11 +51,14 @@
     if !@spawned
       @sprite.position.x = state.x
       @sprite.position.y = state.y
+      @sprite.shadow.position.x = @sprite.position.x
+      @sprite.shadow.position.y = @sprite.position.y + 6
       @spawned = true
     else
       blend = @game.add.tween(@sprite)
       blend.to({ x: state.x, y: state.y }, @rate, Phaser.Easing.Linear.None, true, 0, 0)
-    @blend = @game.add.tween(@sprite)
+      blend = @game.add.tween(@sprite.shadow)
+      blend.to({ x: state.x, y: state.y + 6 }, @rate, Phaser.Easing.Linear.None, true, 0, 0)
     @possessorId = state.possessorId
     @catchable = state.catchable

@@ -67,6 +70,7 @@

   remove: ->
     @sprite.kill()
+    @sprite.shadow.kill()

   controlledUpdate:->
     return unless @sprite.alive


Default screen size to device

Default screen size to device

--- src/coffee/main.coffee
+++ src/coffee/main.coffee
@@ -1,6 +1,6 @@
 $(document).ready ->
-  screenWidth = 1920
-  screenHeight = 1280
+  screenWidth = window.screen.availWidth
+  screenHeight = window.screen.availHeight

   foregroundColor = 'ffffff'
   backgroundColor = '000000'


Adding Color.lookup

Adding Color.lookup

--- src/color_table.coffee
+++ src/color_table.coffee
@@ -2528,4 +2528,7 @@

   names.each (element) ->
     lookup[normalizeKey(element[1])] = parseHex(element[0])
+
+  Color.lookup = (color) ->
+    lookup[normalizeKey(color)]
 )()


Array.isArray() is not implemented in older browsers

--- src/JSOG.coffee
+++ src/JSOG.coffee
@@ -6,6 +6,9 @@
 JSOG = {}

 nextId = 1
+
+# Older browser compatibility
+isArray = Array.isArray || (obj) -> toString.call(obj) == '[object Array]'

 #
 # Take a JSON structure with cycles and turn it into a JSOG-encoded structure. Adds
@@ -44,7 +47,7 @@

 		if not original?
 			return original
-		else if Array.isArray(original)
+		else if isArray(original)
 			return encodeArray(original)
 		else if typeof original == 'object'
 			return encodeObject(original)
@@ -88,7 +91,7 @@

 		if not encoded?
 			return encoded
-		else if Array.isArray(encoded)
+		else if isArray(encoded)
 			return decodeArray(encoded)
 		else if typeof encoded == 'object'
 			return decodeObject(encoded)


minor reformat objective

minor reformat objective

--- coffee/performance_functions.coffee
+++ coffee/performance_functions.coffee
@@ -51,7 +51,9 @@
 parameter5.objective = (X) ->
   x = X[0]
   y = X[1]
-  (-20*Math.exp(-0.2*Math.sqrt(0.5*(x*x+y*y)))-Math.exp(0.5*(Math.cos(2*Math.PI*x)+Math.cos(2*Math.PI*y)))+20+Math.exp(1))
+  (-20*Math.exp(-0.2*Math.sqrt(0.5*(x*x+y*y)))-
+    Math.exp(0.5*(Math.cos(2*Math.PI*x)+Math.cos(2*Math.PI*y)))+
+    20+Math.exp(1))
 parameter5.search_space = [(min: -5, max: 5),(min: -5, max: 5)]
 parameter5.number_of_dimensions = 2
 parameter5.number_of_particles = 64


Trigger sync, as we might have cancelled an earlier request (otherwise showing loading indicator indefinitely)

--- core/app/backbone/collections/search_collection.coffee
+++ core/app/backbone/collections/search_collection.coffee
@@ -6,6 +6,7 @@
   makeEmpty: ->
     @query = ''
     @reset []
+    @trigger 'sync'

   throttle = (method) -> _.throttle method, 300, leading: false
   searchFor: throttle (query) ->


Avoid emitting links too closely in time.

Use robot.brain to store the last time each ticket has been seen; if
that's too recent, avoid emitting a new link.

--- scripts/linkticket.coffee
+++ scripts/linkticket.coffee
@@ -32,6 +32,11 @@
   ticketIds = Array.from(new Set(msg.match))
   for ticketId in ticketIds
     ticketId = ticketId.toUpperCase()
+    last = robot.brain.get(ticketId)
+    now = moment()
+    robot.brain.set(ticketId, now)
+    if last and now.isBefore last.add(1, 'minute')
+      return
     urlstr="https://jira.lsstcorp.org/rest/api/latest/issue/#{ticketId}"
     robot.http(urlstr).get() (err, res, body) ->
       if res.statusCode in [401, 403]


Lead with apm login

Lead with apm login

--- src/auth.coffee
+++ src/auth.coffee
@@ -17,5 +17,5 @@

     callback """
       No GitHub API token in keychain
-      Set the `ATOM_ACCESS_TOKEN` environment variable or run `apm login`.
+      Run `apm login` or set the `ATOM_ACCESS_TOKEN` environment variable.
     """


Remove "compile:haml" task from "compile" task

--- gulp/tasks/compile.coffee
+++ gulp/tasks/compile.coffee
@@ -2,7 +2,7 @@
 config = require("../config")
 $      = config.plugins

-gulp.task("compile", ["compile:babel", "compile:coffee", "compile:scss", "compile:haml"])
+gulp.task("compile", ["compile:babel", "compile:coffee", "compile:scss"])

 gulp.task "compile:babel", ["lint:es"], ->
   gulp.src(config.src.babel)


refocus on cancel

refocus on cancel

--- lib/provider/select-files.coffee
+++ lib/provider/select-files.coffee
@@ -28,6 +28,8 @@

   initialize: ->
     @clientUi = @options.clientUi
+    @ui.onDidDestroy =>
+      @clientUi.focus(autoPreview: false) if @clientUi.isAlive()

   getItems: ->
     items = []


actually share url is better

actually share url is better

--- lib/repliers/query_runner.coffee
+++ lib/repliers/query_runner.coffee
@@ -45,7 +45,7 @@

     renderableFields = dimension_like.concat(measure_like)

-    shareUrl = "*<#{query.share_url}|View in Looker>*"
+    shareUrl = "<#{query.share_url}>"

     renderField = (f, row) =>
       d = row[f.name]


change implementation + doc

change implementation + doc

--- src/core/driver.coffee
+++ src/core/driver.coffee
@@ -2,10 +2,17 @@
 router = require('./router')
 BaseClass = require('./util/baseClass')

+# Base class for all drivers.
 class Driver extends BaseClass
+  # Constructs a new Driver
+  # @param [Object] options the driver options
+  # @option options [Function] parser a function who receives an input and parse it to the format {sender:sender , receiver: receiver ,message:message}
   constructor: (options) ->
-    {@parser} = options
-  send: (sender,receiver,message)->
-    router.send(sender,receiver,message)
+    {parser} = options
+    # Takes the arguments (i.e request,response on http requests or other arguments for different communication protocols / framework), parses it, and then sends the message to the right actor
+    # @param [Arguments] args the arguments to build the message
+    @send= (args...)->
+      {sender,receiver,message} = parser(args...)
+      router.send(sender,receiver,message)

 module.exports = Driver


object: add property definer

object: add property definer

o = new KDObject
o.define 'height', => window.height - @wrapper.height

--- src/core/object.coffee
+++ src/core/object.coffee
@@ -22,6 +22,14 @@

     @on 'error', error
     @once 'ready', => @readyState = READY
+
+
+  define: (property, options) ->
+
+    options = { get: options }  if 'function' is typeof options
+
+    Object.defineProperty this, property, options
+

   bound: (method)->
     unless 'function' is typeof @[method]


fix addons for footer clone wirh props

fix addons for footer clone wirh props

--- src/components/exercise/step-card.cjsx
+++ src/components/exercise/step-card.cjsx
@@ -1,4 +1,4 @@
-React = require 'react'
+React = require 'react/addons'
 _ = require 'underscore'

 classnames = require 'classnames'


Modal: done the forgotten refactoring on ::addSubView

--- src/components/modals/modalview.coffee
+++ src/components/modals/modalview.coffee
@@ -86,9 +86,9 @@
       </div>
     """

-  addSubView:(view, selector = ".kdmodal-content")->
+  addSubView:(view, selector = ".kdmodal-content", shouldPrepend = no)->
     selector = null if @$(selector).length is 0
-    super view, selector
+    super view, selector, shouldPrepend

   setButtons:(buttonDataSet, destroyExists = no)->
     @buttons or= {}


set tabIndex

set tabIndex

--- src/components/tri-state-checkbox.cjsx
+++ src/components/tri-state-checkbox.cjsx
@@ -14,15 +14,19 @@
     type: React.PropTypes.oneOf(_.keys(ICON_TYPES)).isRequired
     onClick: React.PropTypes.func

+  onClick: (ev) ->
+    ev.preventDefault()
+    @props.onClick?(ev)
+
   render: ->
     if @props.onClick
       styles = cursor: 'pointer'
-
-    <Icon
-      type={ICON_TYPES[@props.type]}
-      onClick={@props.onClick}
-      style={styles}
-    />
-
+    <span tabIndex={1} className='tri-state-checkbox' onClick={@onClick}>
+      <Icon
+        type={ICON_TYPES[@props.type]}
+        onClick={@props.onClick}
+        style={styles}
+      />
+    </span>

 module.exports = TriStateCheckbox


Prepare code to reconfigure push plugin in runtime

--- server/lib/cordova.coffee
+++ server/lib/cordova.coffee
@@ -44,7 +44,8 @@
 			params: [tokens]

-Meteor.startup ->
+configurePush = ->
+	console.log 'configuring push'

 	Push.debug = RocketChat.settings.get 'Push_debug'

@@ -123,3 +124,30 @@
 			pushGetway = DDP.connect(RocketChat.settings.get('Push_gateway'), {_dontPrintErrors: false})

 		Push.enabled = true
+
+
+Meteor.startup ->
+	configurePush()
+
+	## Prepared to reconfigure the push plugin
+	#
+	# keys = [
+	# 	'Push_enable'
+	# 	'Push_enable_gateway'
+	# 	'Push_gcm_api_key'
+	# 	'Push_gcm_project_number'
+	# 	'Push_apn_passphrase'
+	# 	'Push_apn_key'
+	# 	'Push_apn_cert'
+	# 	'Push_production'
+	# 	'Push_apn_dev_passphrase'
+	# 	'Push_apn_dev_key'
+	# 	'Push_apn_dev_cert'
+	# 	'Push_gateway'
+	# ]
+
+	# configurePushDebounce = _.debounce Meteor.bindEnvironment(configurePush), 1000
+
+	# RocketChat.settings.onload keys, ->
+	# 	configurePushDebounce()
+


Cleanup domain in reset password message

--- server/lib/helpers.coffee
+++ server/lib/helpers.coffee
@@ -16,9 +16,14 @@
     # must be required here to prevent cross dependency resolution
     localization = require './localization_manager'

+    # cleanup domain: remove https and port number if needed
+    domain = instance.domain
+    if domain.indexOf('https://') isnt -1
+        domain = domain.substring(8, domain.length)
+    domain = domain.split(':')[0]
     data =
         from: localization.t 'reset password email from',
-            domain: instance.domain
+            domain: domain
         subject: localization.t 'reset password email subject'
         content: localization.t 'reset password email text',
             domain: instance.domain


:green_heart: Fix random failures in tests due to setup

--- spec/minimap-v4-spec.coffee
+++ spec/minimap-v4-spec.coffee
@@ -2,14 +2,22 @@
 Minimap = require '../lib/minimap'

 describe 'Minimap package v4', ->
-  [workspaceElement, minimapPackage] = []
+  [editor, minimap, editorElement, minimapElement, workspaceElement, minimapPackage] = []

   beforeEach ->
     atom.config.set 'minimap.v4Preview', true

+    workspaceElement = atom.views.getView(atom.workspace)
+
+    waitsFor ->
+      atom.workspace.open('sample.coffee')
+
     waitsFor -> atom.packages.activatePackage('minimap')

+    waitsFor -> workspaceElement.querySelector('atom-text-editor')
     runs ->
+      editor = atom.workspace.getActiveTextEditor()
+      editorElement = atom.views.getView(editor)
       minimapPackage = atom.packages.getLoadedPackage('minimap').mainModule

   afterEach ->
@@ -29,22 +37,6 @@
     expect(minimapElement).toExist()

   describe 'when an editor is opened', ->
-    [editor, minimap, editorElement, minimapElement] = []
-
-    beforeEach ->
-      waitsFor ->
-        atom.workspace.open('sample.coffee')
-
-      runs ->
-        workspaceElement = atom.views.getView(atom.workspace)
-        jasmine.attachToDOM(workspaceElement)
-
-      waitsFor -> atom.workspace.getActiveTextEditor()
-
-      runs ->
-        editor = atom.workspace.getActiveTextEditor()
-        editorElement = atom.views.getView(editor)
-
     it 'creates a minimap model for the editor', ->
       expect(minimapPackage.minimapForEditor(editor)).toBeDefined()


Making the feature flag handler based like accounts

--- server/server_flag.coffee
+++ server/server_flag.coffee
@@ -1,44 +1,53 @@
 if Meteor.isServer
     flags = {} || Meteor.settings.featureFlags
+    flagHandlers = {}
+    @FeatureFlag = {}
+    @FeatureFlag.registerFlagHandler = (identifier, handler) ->
+        flagHandlers[identifier] = handler
+    @FeatureFlag.featureFlag = (flagType) ->
+        val = flags[flagType]
+        for identifier, handler in flagHandlers
+            return true if handler(val) == true
+        return false

-    @featureFlag = (flag) ->
+    ###
+    # Add a feature flag handler for email domains
+    #
+    # This would look like  {flag: ['gmail.com']} or {flag: 'gmail.com'}
+    ###
+    @FeatureFlag.registerFlagHandler("email-domains", (val) ->
+        user = Meteor.user()
+        return false if !user?
+        email = getEmail(user)
+        return false if !email?
+        filterset = if _.isArray(val.email_domains) then val.email_domains else [val.email_domains]
+        valid = (item for item in filterset when email.search(item) > 0)
+        if valid.length > 0 then return true else return false
+    )
+    ###
+    # Add a feature flag for users based on email
+    #
+    # This would look like {flag: ['bob@loblaw.com']} or {flag:
+    # 'support@faceblock.com'}
+    ###
+    @FeatureFlag.registerFlagHandler('users', (val) ->
+        user = Meteor.user()
+        return false if !user?
+        email = getEmail(user)
+        return false if !email?
+        filterset = if _.isArray(val.users) then val.users else [val.users]
+        valid = (item for item in filterset when email == item)
+        if valid.length > 0 then return true else return false

-       val = flags[flag]
-       return true if val is null or val is undefined
-       user = Meteor.user()
-       filterset = []
-       final = false
-
-       if val.email_domains?
-           logger.info "Flag #{flag} on email"
-           return false if !user?
-           email = getEmail user
-           return false if !email?
-           filterset = if _.isArray(val.email_domains) then val.email_domains else [val.email_domains]
-           valid = (item for item in filterset when email.search(item) > 0)
-           final = true if valid.length > 0
-      if val.schools?
-           logger.info "Flag #{flag} on schools"
-           return false if !user?
-           filterset = if _.isArray(val.schools) then val.schools else [val.schools]
-           for school in user.schools
-               valid = (item for item in filterset when item is school.name)
-           final = true if valid.length > 0
-      if val.users?
-           logger.info "Flag #{flag} for specific users"
-           return false if !user?
-           email = getEmail user
-           return false if !email?
-           filterset = if _.isArray(val.users) then val.users else [val.users]
-           valid = (item for item in filterset when email == item)
-           final = true if valid.length > 0
-      if val == "admins"
-           logger.info "Flag #{flag} on admins"
-           return user?.rights?.admin? == true
-      if val == true or val == false
-           logger.info "Flag #{flag} on/off"
-           return val
-      return final
+    )
+    ###
+    # Simple boolean flag for features
+    #
+    # This would look like {flag: true} or {flag: false}
+    ###
+    @FeatureFlag.registerFlagHandler('onOff', (val) ->
+        return val
+    )

     getEmail = (user) ->
         if user.emails?
@@ -46,13 +55,10 @@
         else if user.services?.google?
             user.services.google.email

-    flagsForUser = () ->
+  Meteor.methods(
+      flagsForUser: () ->
         final = {}
         for k, v of flags
-            final[k] = featureFlag(k)
+            final[k] = @FeatureFlag.featureFlag(k)
         return final
-
-  Meteor.methods(
-      flagsForUser: () ->
-          return flagsForUser()
   )


editor template: add use of syncdb so file state is reflected in editor; add lots of comments explaining how this all works

--- src/smc-webapp/editor_template.cjsx
+++ src/smc-webapp/editor_template.cjsx
@@ -14,49 +14,147 @@
 ###

+# The immutable.js library, which is used to store data.
+immutable = require('immutable')
+
+# React bootstrap widgets.
+{Button, Input, Panel, Row, Col} = require('react-bootstrap')
+
+# Hooking into SMC's react support.
 {React, ReactDOM, rclass, rtypes, Redux, Actions, Store}  = require('./smc-react')
-{Button, Panel, Row, Col} = require('react-bootstrap')
-{Icon, Space} = require('./r_misc')
+
+# Helper functions
 misc = require('smc-util/misc')
-{defaults, required} = misc

+# The name for redux of this editor.  This controls where in the redux store,
+# which is an immutable.js map, that data about this editor is located.
 redux_name = (project_id, path) ->
     return "editor-#{project_id}-#{path}"

+# The Actions object
 class TemplateActions extends Actions
     init: (@project_id, @filename) =>
-        @path = misc.path_split(@filename).head
+        # Set a sample property to a default value.
         @setState(sample_property: 'foo')

+    # Whenever the underlying syncdb changes (due to a change locally or by another user),
+    # this funtion gets called.  The changes then get set in the redux store.
+    _syncdb_change: (changes) =>
+        s = s0 = @redux.getStore(@name).get('syncdb')
+        for x in changes
+            if x.insert?
+                s = s.set(x.insert.id, immutable.fromJS(x.insert.val))
+            else if x.remove?
+                s = s.delete(x.remove.id)
+        if s != s0
+            # State actually changed, so change the store.
+            @setState(syncdb: s)
+
+    # We treat the syncdb as a key:value store.   You could do something more complicated if necessary.
+    set: (id, val) =>
+        @syncdb.update
+            set :
+                val : val
+            where :
+                id : id
+        @syncdb.save()
+
+# Create the redux action, store, and syncdb connection.
+
 exports.init_redux = init_redux = (redux, project_id, filename) ->
-    name = redux_name(project_id, filename)
+    name = redux_name(project_id, filename)  # name of all this stuff
     if redux.getActions(name)?
         return  # already initialized
+
+    # Create the actions for this file.
     actions = redux.createActions(name, TemplateActions)
     actions.init(project_id, filename)
-    redux.createStore(name)

+    # The store
+    store = redux.createStore(name)
+
+    # Use the corresponding file on disk as a synchronized database.
+    require('./syncdb').synchronized_db
+        project_id    : project_id
+        filename      : filename
+        cb            : (err, syncdb) =>
+            if err
+                alert_message(type:'error', message:"unable to open #{@filename}")
+            else if not syncdb.valid_data
+                alert_message(type:'error', message:"json in #{@filename} is broken")
+            else
+                # Here we use the syncdb as a key value store.  This is NOT the only
+                # way to use the syncdb; you can do anything you want with it, as it is
+                # just an array of distinct Javascript objects.
+                s = immutable.fromJS({})
+                for x in syncdb.select()
+                    s = s.set(x.id, x.val)
+                actions.setState(syncdb : s)
+
+                # Whenever the syncdb is changed, _syncdb_change is called to take
+                # into account this change (in the store).
+                syncdb.on('change', actions._syncdb_change)
+                store.syncdb = actions.syncdb = syncdb
+
+
+# This is a template for the component that includes the editor.
 Template = (name) -> rclass
-    reduxProps:
+    reduxProps:  # the Template react component will be updated whenever syncdb or sample_propery change in the store.
         "#{name}" :
+            syncdb          : rtypes.immutable
             sample_property : rtypes.string

     propTypes:
         actions : rtypes.object

+
+    # For a demo, we render the data in the syncdb immutable map, with the keys in sorted order.
+    render_syncdb: ->
+        if not @props.syncdb?
+            return
+        v = ([id,val] for id, val of @props.syncdb.toJS())
+        v.sort (a,b) -> misc.cmp(a[0], b[0])
+        for x in v
+            v.push
{x[0]} {"#{misc.to_json(x[1])}"}

+        return v
+
+    # Something to allow the user to add an entry to the syncdb.
+    add_entry: ->
+        v = misc.split(@refs.input.getValue())
+        @props.actions.set(v[0], v[1])
+
+    render_add_entry: ->
+

+            <Input key="input"
+                autoFocus
+                type        = 'text'
+                ref         = 'input'
+                placeholder = 'id value'
+                style       = {width:'30em'}
+            />
+            <Button onClick={@add_entry}>Add Entry</Button>
+

+
     render : ->

Template for an editor -- {@props.sample_property}

+

+            {@render_add_entry()}
+

+            {@render_syncdb()}

+# Actually render the editor component:
 render = (redux, project_id, path) ->
-    name    = redux_name(project_id, path)
+    name    = redux_name(project_id, path)  # name of the store/actions
     actions = redux.getActions(name)
-    Template_connected = Template(name)
+    Template_connected = Template(name)     # the component, but with the redux props hooked in
+    # Finally create the component hooked into redux, so that it autoupdates whenever certain keys in the store change.
     <Redux redux={redux}>
         <Template_connected actions={actions} />
     </Redux>

+# Boilerplate for interacting with non-React app.
 exports.free = (project_id, path, dom_node, redux) ->
     ReactDOM.unmountComponentAtNode(dom_node)


sagews: disable require.ensure for d3 (plotting graphs) -- #2860

--- src/smc-webapp/sagews/sagews.coffee
+++ src/smc-webapp/sagews/sagews.coffee
@@ -1526,7 +1526,9 @@
         if mesg.d3?
             e = $("<div>")
             output.append(e)
-            require.ensure [], () =>
+            # TODO this is a hotfix. Make this loading lazy again -- #2860
+            #require.ensure [], () =>
+            if true
                 require('./d3')  # install the d3 plugin
                 e.d3
                     viewer : mesg.d3.viewer


Add XGBoost model metrics.

Add XGBoost model metrics.

--- src/ext/components/model-output.coffee
+++ src/ext/components/model-output.coffee
@@ -470,7 +470,7 @@
             if confusionMatrix = output.cross_validation_metrics?.cm?.table
               renderMultinomialConfusionMatrix 'Cross Validation Metrics - Confusion Matrix', confusionMatrix

-      when 'gbm', 'drf', 'svm'
+      when 'gbm', 'drf', 'svm', 'xgboost'
         if table = _.inspect 'output - Scoring History', _model
           if table.schema['validation_logloss'] and table.schema['training_logloss']
             renderPlot 'Scoring History - logloss', no, _.plot (g) ->
@@ -609,7 +609,7 @@
               renderMultinomialConfusionMatrix 'Validation Metrics - Confusion Matrix', confusionMatrix
             if confusionMatrix = output.cross_validation_metrics?.cm?.table
               renderMultinomialConfusionMatrix 'Cross Validation Metrics - Confusion Matrix', confusionMatrix
-    # end of when 'gbm', 'drf', 'svm'
+    # end of when 'gbm', 'drf', 'svm', 'xgboost'

       when 'stackedensemble'
         if table = _.inspect 'output - training_metrics - Metrics for Thresholds', _model


Speed up expanding sinle panel in templates

--- app/assets/javascripts/activeadmin_ancestry_view/base.coffee
+++ app/assets/javascripts/activeadmin_ancestry_view/base.coffee
@@ -36,6 +36,7 @@
           document.styleSheets[0].addRule ruleName, properies, 0
         else
           document.styleSheets[0].insertRule ruleName + " { #{properies} }", 0
+
   # find half height of the node (px)
   middleOfHeight = (node) ->
     Math.abs $(node).height() / 2
@@ -102,14 +103,28 @@
       if $(this).hasClass('selectable')
         $(this).removeClass('selectable')

+  similarItems = (arr1, arr2) ->
+    item for item in arr2 when item in arr1
+
+  getElements = (arrayOfIds) ->
+    $.map(arrayOfIds, (id) ->
+      $("##{id}").get()
+    )
+
   changeVerticalOfEachParent = (nodeToStart) ->
-    # true in (val in array1 for val in array2)
-    # prevRootId  = $(nodeToStart).prev('.panel-root').attr 'id'
-    # prevParents = $(nodeToStart).prevAll(".panel-parent.#{prevRootId}")
-    prevParents = $(nodeToStart).prevAll('.panel-parent')
-    actualCollection = prevParents.add(nodeToStart)
-
-    actualCollection.each (i, element) ->
+    # find actual parents
+    nodeClasses  = $(nodeToStart).attr('class').split(' ')
+    parents      = $(nodeToStart).prevAll('.panel-parent')
+    parentIds    = $.map(parents, (el) -> $(el).attr 'id')
+
+    # get actual parent ids
+    actualIds = similarItems(nodeClasses, parentIds)
+    # add node id to array
+    actualIds.push $(nodeToStart).attr('id')
+
+    actualCollection = getElements(actualIds)
+
+    $.each(actualCollection, (i, element) ->
       nodeId = $(element).attr('id')
       lastChild = $(element).parent().find(
         ".panel-container[data-last-child=#{nodeId}]")
@@ -118,6 +133,7 @@
         newDistance = verticalBranchDist(element, lastChild)
         line = getCSSRule('[id="' + nodeId + '"]::before')
         line.style.height = newDistance.toString().concat('px')
+    )

   # inherit color from parent panel on load
   # if parent has no color, inherit from parent of parent
@@ -141,12 +157,10 @@
     if content.is(':hidden')
       content.show 0, ->
         uptHorizontalLine(node) unless $(node).hasClass('panel-root')
-        changeVerticalOfEachParent(node)
-
     else
       content.hide 0, ->
         uptHorizontalLine(node) unless $(node).hasClass('panel-root')
-        changeVerticalOfEachParent(node)
+    changeVerticalOfEachParent(node)

   # show-hide nodes
   $('.show-childrens').click ->
@@ -162,17 +176,16 @@
     $('.panel').each ->
       node = $(this).parent('.panel-container')
       if node.hasClass(parentId)
-
         content = $(this).find('.panel_contents')
         if parentContent.is(':visible')
           content.show 0, ->
             uptHorizontalLine(node) unless $(node).hasClass('panel-root')
             changeVerticalOfEachParent(node)
-
         else
           content.hide 0, ->
             uptHorizontalLine(node) unless $(node).hasClass('panel-root')
             changeVerticalOfEachParent(node)
+      # todo: speed up: 1) by self vert. line; 2) by self + parent

   # select user
   $('.panel-header').on 'click', ->


Handle rename with context menu on the first taxon ie. the root one

Also rename the main input field (sort of title) when updating the root taxon name with contextual menu

--- app/assets/javascripts/admin/spree/taxons/taxonomy.js.coffee
+++ app/assets/javascripts/admin/spree/taxons/taxonomy.js.coffee
@@ -49,6 +49,8 @@
   last_rollback = data.rlbk
   node = data.rslt.obj
   name = data.rslt.new_name
+  # change the name inside the main input field as well if taxon is the root one
+  document.getElementById("taxonomy_name").value = name if node.parents("[id]").attr("id") == "taxonomy_tree"

   url = new URL(base_url)
   url.pathname = url.pathname + '/' + node.attr("id")


Fix init link dialog if used in tinymce

Fix init link dialog if used in tinymce

If used inside a tinymce editor we do not have these
attributes. We only have them from linked essence or
ingredient.

Closes #2198

--- app/assets/javascripts/alchemy/alchemy.link_dialog.js.coffee
+++ app/assets/javascripts/alchemy/alchemy.link_dialog.js.coffee
@@ -4,12 +4,13 @@
 class window.Alchemy.LinkDialog extends Alchemy.Dialog

   constructor: (@link_object) ->
-    parent_selector = @link_object.dataset.parentSelector
-    parent = document.querySelector(parent_selector)
-    @link_value_field = parent.querySelector("[data-link-value]")
-    @link_title_field = parent.querySelector("[data-link-title]")
-    @link_target_field = parent.querySelector("[data-link-target]")
-    @link_class_field = parent.querySelector("[data-link-class]")
+    if @link_object.dataset
+      parent_selector = @link_object.dataset.parentSelector
+      parent = document.querySelector(parent_selector)
+      @link_value_field = parent.querySelector("[data-link-value]")
+      @link_title_field = parent.querySelector("[data-link-title]")
+      @link_target_field = parent.querySelector("[data-link-target]")
+      @link_class_field = parent.querySelector("[data-link-class]")
     @url = Alchemy.routes.link_admin_pages_path
     @$link_object = $(@link_object)
     @options =


Coffee semantics

Coffee semantics

--- drop.coffee
+++ drop.coffee
@@ -4,7 +4,7 @@

 $ = jQuery
-isIE = !!/msie [\w.]+/.exec navigator.userAgent.toLowerCase()
+isIE = not not /msie [\w.]+/.exec navigator.userAgent.toLowerCase()

 # Extracted from jQuery UI Core (to remove dependency)


localStorage save track info

localStorage save track info

--- src/app/tracks/tracks.service.coffee
+++ src/app/tracks/tracks.service.coffee
@@ -1,5 +1,6 @@
 angular.module 'jaMusic1'
-  .factory 'Tracks', ($http, env, Common) ->
+  .factory 'Tracks', ($http, env, Common, $localStorage, $q) ->
+    storage = $localStorage

     getTrackFile = (id = 1204669) ->
       url = "#{env.url}#{env.version}/tracks/file/?client_id=#{env.clientId}&audioformat=mp31&id=#{id}&action=stream"
@@ -12,10 +13,20 @@
         .catch(Common.getError)

     getTrackInfo = (id = 1204669) ->
+      cache = storage["track_#{id}"]
+
+      if cache
+        defer = $q.defer()
+        defer.resolve cache
+        return defer.promise
+
       url = "#{env.url}#{env.version}/tracks/?client_id=#{env.clientId}&id=#{id}"

       $http.get(url)
-        .then(Common.getSuccess)
+        .then((response) ->
+          storage["track_#{id}"] = response.data
+          return response.data
+        )
         .catch(Common.getError)

     searchTracks = (query = '') ->


rng/objects class/function cleanup

rng/objects class/function cleanup

--- src/textorum/relaxng/objects.coffee
+++ src/textorum/relaxng/objects.coffee
@@ -28,7 +28,7 @@
       return node
     if not node
       return new NotAllowed()
-    foo = switch node.local
+    pattern = switch node.local
       when 'element' then new Element children[0], children[1]
       when 'define' then new Define _getAttr(node, "name"), children[0]
       when 'notAllowed' then new NotAllowed()
@@ -43,15 +43,15 @@
       when 'choice' then new Choice children[0], children[1]
       when 'group' then new Group children[0], children[1]
       when 'interleave' then new Interleave children[0], children[1]
-      when "anyName" then new AnyName node, children[0]
-      when "nsName" then new NsName node, children[0]
-      when "name" then new Name node, children[0]
+      when "anyName" then new AnyName children[0]
+      when "nsName" then new NsName _getAttr(node, "ns"), children[0]
+      when "name" then new Name _getAttr(node, "ns"), children[0]
       when "choice" then new Choice children[0], children[1]
       when 'except' then getPattern children[0]
       when 'param' then new Param _getAttr(node, "name"), children[0]
       else
         throw new RNGException("can't parse pattern for #{node.local}")
-    foo
+    pattern

   class RNGException extends Error
@@ -73,7 +73,7 @@
   class NameClass

   class AnyName extends NameClass
-    constructor: (nameClassNode, exceptPattern) ->
+    constructor: (exceptPattern) ->
       @except = getPattern exceptPattern
     toString: =>
       if @except instanceof NotAllowed
@@ -82,8 +82,7 @@
         "* - #{@except}"

   class Name extends NameClass
-    constructor: (nameClassNode, @name) ->
-      @ns = _getAttr nameClassNode, "ns"
+    constructor: (@ns, @name) ->
     toString: =>
       if @ns
         "#{@ns}:#{@name}"
@@ -91,8 +90,7 @@
         "#{@name}"

   class NsName extends NameClass
-    constructor: (nameClassNode, exceptPattern) ->
-      @ns = _getAttr nameClassNode, "ns"
+    constructor: (@ns, exceptPattern) ->
       @except = getPattern exceptPattern
     toString: =>
       if @except instanceof NotAllowed
@@ -219,29 +217,19 @@
     toString: =>
       "#{@name} = #{@pattern}"

-
-  #** QName - label for elements and attributes
-
-  class QName
-    constructor: (@uri, @localName) ->
-
-  #** XML document classes
-
-  class ChildNode
-
-  class ElementNode extends ChildNode
-    constructor: (@qName, @context, @attributeNodes, @childNodes) ->
-
-  class TextNode extends ChildNode
-    constructor: (@string)
-
-  class AttributeNode
-    constructor: (@qName, @string) ->
-
-  { _getAttr, getPattern, AnyName, Attribute, AttributeNode,
-    ChildNode, Choice, Context, Data, Datatype, Define,
-    Empty,
-    Element, ElementNode, Group, Interleave, List, MissingContent,
+  { _getAttr, getPattern,
+    AnyName, Attribute,
+    Choice, Context,
+    Data, Datatype, Define,
+    Empty, Element,
+    Group,
+    Interleave,
+    List,
+    MissingContent,
     Name, NameClass, NotAllowed, NsName,
-    OneOrMore, Param, Pattern, QName, Ref, Text, TextNode,
-    Value }
+    OneOrMore,
+    Param, Pattern,
+    Ref,
+    Text,
+    Value
+  }


Trix.DocumentView.render(document) -> TrixDocumentElement

--- src/trix/views/document_view.coffee
+++ src/trix/views/document_view.coffee
@@ -1,8 +1,15 @@
 #= require trix/views/block_view

-{defer, walkTree} = Trix
+{defer, makeElement} = Trix

 class Trix.DocumentView extends Trix.ObjectView
+  @render: (document) ->
+    element = makeElement("trix-document")
+    element.removeAttribute("contenteditable")
+    view = new this document, {element}
+    view.render()
+    element
+
   constructor: ->
     super
     @document = @object


added stuff for no internet view

added stuff for no internet view

--- Framework/MobileAppBase.coffee
+++ Framework/MobileAppBase.coffee
@@ -8,7 +8,7 @@
       viewBackgroundGradient: null
       viewTitleBarColor: '#000000'
       debugMode: false
-      internetRequired: true
+      noInternetViewEnabled: false
     }, options)

     if @settings.googleAnalyticsID
@@ -19,10 +19,7 @@
     @xhr = Ti.Network.createHTTPClient({ timeout: 15000 })
     @includedFiles = []

-    if @settings.internetRequired
-      @noInternetView = @create("NoInternetView")
-      @checkInternet()
-      Ti.Network.addEventListener('change', (e) => @checkInternet())
+    Ti.Network.addEventListener('change', (e) => @checkInternet())

   delay: (ms, func) -> setTimeout func, ms
   create: (className, options = {}) -> @classFactory.create(className, options)
@@ -36,12 +33,25 @@
     @xhr.onerror = () ->
       onError() if onError
     @xhr.send(params)
+

+  noInternetEnable: =>
+    @settings.noInternetViewEnabled = true
+    if !@noInternetView?
+      @noInternetView = @create("NoInternetView")

-  checkInternet: ->
-    if(Ti.Network.online)
+    @checkInternet()
+
+  noInternetDisable: =>
+    @settings.noInternetViewEnabled = false
+    @checkInternet()
+
+  checkInternet: =>
+    if Ti.Network.online
       Ti.API.info('Welcome to the internet')
-      @noInternetView.window.close()
+      if @noInternetView?
+        @noInternetView.window.close()
     else
       Ti.API.info('Bloody hell, the network just DISSAPEARED!')
-      @noInternetView.window.open()+      if @noInternetView? && @settings.noInternetViewEnabled
+        @noInternetView.window.open()


Fix dhb create modal "name" / "rename" label switch

--- src/components/dashboard/dashboard.directive.coffee
+++ src/components/dashboard/dashboard.directive.coffee
@@ -127,6 +127,7 @@
       self.model = { name: '' }
       self.errors = ''
       self.isLoading = false
+      $scope.createDashboardModal.isTemplate = false
       self.dhbLabelName = ImpacTheming.getDhbLabelName()
       self.instance = $uibModal.open(self.config)

@@ -221,6 +222,7 @@
       )

     $scope.createDashboardModal.onSelectTemplate = ({ template })->
+      $scope.createDashboardModal.isTemplate = !_.isEmpty(template)
       $scope.createDashboardModal.model = template

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


Cache the $page and implement app.show for rendering a view to the $page

--- SingularityUI/app/application.coffee
+++ SingularityUI/app/application.coffee
@@ -18,6 +18,9 @@
     initialize: ->
         app.isMobile = touchDevice = 'ontouchstart' of document.documentElement

+        app.$page = $('#page')
+        app.page = app.$page[0]
+
         @views = {}
         @collections = {}

@@ -35,6 +38,12 @@
                 root: '/singularity/'

             Object.freeze? @
+
+    show: (view) ->
+        if app.page.children.length
+            app.page.replaceChild view.el, app.page.children[0]
+        else
+            app.page.appendChild view.el

     fetchResources: (success) ->
         @resolveCountdown = 0


Fix for positioning when component resizes.

--- src/coffee/ux/callout/Callout.coffee
+++ src/coffee/ux/callout/Callout.coffee
@@ -182,6 +182,28 @@
 		return

 	###*
+	@inheritdoc
+	###
+	onResize: () ->
+		@callParent()
+		if @getTarget() and @isVisible()?
+			elementOrComponent = if Ext.isString( @getTarget() ) then Ext.ComponentQuery.query( @getTarget() )[0] else @getTarget()
+			@getEl().anchorTo(
+				elementOrComponent.el || elementOrComponent,
+				@getRelativePosition(),
+				@getRelativeOffsets() || [ 0, 0 ],
+				false,
+				50,
+				Ext.bind(
+					->
+						@afterSetPosition( @getEl().getLeft(), @getEl().getRight() )
+						return
+					@
+				)
+			)
+		return
+
+	###*
 	@protected
 	Handles a 'mousedown' event on the current HTML document.
 	###


Added looping

Added looping

--- src/coffee/views/youtube_view.coffee
+++ src/coffee/views/youtube_view.coffee
@@ -26,5 +26,6 @@
       modestbranding: 1
       rel: 0
       showinfo: 0
+      loop: 1

     "#{BASE_URL}/#{firstId}?#{$.param(params)}"


Updated Instagram Icon to new version

Updated Instagram Icon to new version


Set CoffeeScript sandbox option to true

Set CoffeeScript sandbox option to true

--- src/cson.coffee
+++ src/cson.coffee
@@ -77,7 +77,7 @@
 parseObject = (objectPath, contents) ->
   if path.extname(objectPath) is '.cson'
     CoffeeScript = require 'coffee-script'
-    CoffeeScript.eval(contents, {bare: true})
+    CoffeeScript.eval(contents, {bare: true, sandbox: true})
   else
     JSON.parse(contents)


Fix reindentation on pasting

Fix reindentation on pasting

--- src/js/app/CommandWorker.coffee
+++ src/js/app/CommandWorker.coffee
@@ -24,7 +24,7 @@
       else
         if value.length > 0
           # console.log "from command worker", (@source or '') + value
-          sourceAndCommand = (@source or '') + value
+          sourceAndCommand = (@source or '') + '\n' + value
           try
             # [res, warnings] = compiler.compileExp value
             # [";" + res, warnings])


Save collection form before submit. This way, if the submit hangs, the user doesn't lose everything if they reload. If the submit succeeds, the form is cleared anyway.

--- src/js/collection-editor.coffee
+++ src/js/collection-editor.coffee
@@ -144,6 +144,7 @@
 # submit the form to Fusion Tables
 submit_collection_form = ->
   disable_collection_form()
+  save_collection_form()
   collection = $('#collection_select').val()
   column_names = []
   row_values = []


Pass loading unit test

Pass loading unit test

--- test/spec/directives/loading.coffee
+++ test/spec/directives/loading.coffee
@@ -10,7 +10,8 @@
   beforeEach inject ($controller, $rootScope) ->
     scope = $rootScope.$new()

-  it 'should make hidden element visible', inject ($compile) ->
+  it 'should not be undefined', inject ($compile) ->
     element = angular.element '<loading></loading>'
     element = $compile(element) scope
-    expect(element.text()).toBe 'this is the loading directive'
+    expect !!element
+      .to.be.equal true


Fixed saving task in addObject. Should be saved after receiving instance number rather than before.

--- tasks.coffee
+++ tasks.coffee
@@ -238,8 +238,7 @@
       task.parameterNames = []
       allDeviceUpdates.instanceName = [["#{task.objectName}.#{task.instanceNumber}", task.instanceName]] if task.instanceName?
     else
-      # Use STATUSS_SAVE to avoid adding object again in case of failure
-      callback(null, STATUS_SAVE, {methodRequest : {type : 'AddObject', objectName : "#{task.objectName}."}})
+      callback(null, STATUS_STARTED, {methodRequest : {type : 'AddObject', objectName : "#{task.objectName}."}})
       return

   subtask = () =>
@@ -255,7 +254,11 @@
             task.subtask = {name : 'getParameterValues', parameterNames : task.parameterNames}
             subtask()
           else if status is STATUS_STARTED
-            callback(err, STATUS_STARTED, cwmpResponse, allDeviceUpdates)
+            if allDeviceUpdates.instanceName?
+              # Use STATUSS_SAVE to avoid adding duplicate object in case of error
+              callback(err, STATUS_SAVE, cwmpResponse, allDeviceUpdates)
+            else
+              callback(err, STATUS_STARTED, cwmpResponse, allDeviceUpdates)
           else
             throw Error('Unexpected subtask status')
         )


Only (optionally) hide the device bezel when running in the app

--- framer/Components/DeviceComponent.coffee
+++ framer/Components/DeviceComponent.coffee
@@ -363,7 +363,7 @@

 	@define "hideBezel",
 		get: ->
-			return @_hideBezel ? false
+			return (@_hideBezel ? false) and Utils.isFramerStudio()
 		set: (hideBezel) ->
 			@_hideBezel = hideBezel
 			if @_hideBezel


add pdfng scroll handler using requestAnimationFrame

--- services/web/public/coffee/ide/pdfng/directives/pdfViewer.coffee
+++ services/web/public/coffee/ide/pdfng/directives/pdfViewer.coffee
@@ -307,26 +307,57 @@
 					# trigger a redraw
 					scope.scale = angular.copy (scope.scale)

+        // shim layer with setTimeout fallback
+        requestAnimationFrame = do () ->
+          window.requestAnimationFrame  ||
+          window.webkitRequestAnimationFrame ||
+          window.mozRequestAnimationFrame    ||
+          (callback) -> window.setTimeout(callback, 1000 / 60)
+
+        rafActive = null
 				element.on 'scroll', () ->
 					#console.log 'scroll event', element.scrollTop(), 'adjusting?', scope.adjustingScroll
+					scope.scrollPosition = element.scrollTop()
 					if scope.adjustingScroll
 						updateContainer()
 						scope.$apply()
 						scope.adjustingScroll = false
 						return
-					scope.scrolled = true
-					if scope.scrollHandlerTimeout
-						$timeout.cancel(scope.scrollHandlerTimeout)
-					scope.scrollHandlerTimeout = $timeout scrollHandler, 100
+					# scope.scrolled = true
+					# if scope.scrollHandlerTimeout
+					#	$timeout.cancel(scope.scrollHandlerTimeout)
+					# scope.scrollHandlerTimeout = $timeout scrollHandler, 100
+					if not rafActive?
+						rafActive = requestAnimationFrame(scrollCheck)
+
+				prev = null
+				scope.scrollPosition = element.scrollTop()
+				scrollCheck = (timestamp) ->
+					prev = timestamp if not prev?
+					dt = timestamp - prev
+					if (dt > 100)
+						scope.lastScrollPosition = scope.scrollPosition if not scope.lastScrollPosition?
+						dy = scope.scrollPosition - scope.lastScrollPosition
+						scope.lastScrollPosition = scope.scrollPosition
+						console.log 'handle scroll dy', dy if dy != 0
+						scope.scrolled = true if dy != 0
+						if scope.scrolled && dy == 0
+							scrollHandler()
+							scope.scrolled = false
+							rafActive = null
+							return
+					requestAnimationFrame(scrollCheck)
+
+				requestAnimationFrame(scrollCheck)

 				scrollHandler = () ->
-					scope.scrollHandlerTimeout = null
 					updateContainer()
 					scope.$apply()
 					newPosition = ctrl.getPdfPosition()
 					if newPosition?
 						scope.position = newPosition
 					scope.$apply()
+					# scope.scrollHandlerTimeout = null

 				scope.$watch 'pdfSrc', (newVal, oldVal) ->
 					# console.log 'loading pdf', newVal, oldVal


add interrupt in editor

add interrupt in editor

--- keymaps/julia-client.cson
+++ keymaps/julia-client.cson
@@ -6,6 +6,7 @@
   'cmd-j cmd-m': 'julia-client:set-working-module'
   'cmd-shift-j cmd-shift-m': 'julia-client:reset-working-module'
   'ctrl-`': 'julia-client:toggle-console'
+  'ctrl-c': 'julia-client:interrupt-julia'

 '.platform-darwin .ink-console':
   'ctrl-c': 'julia-client:interrupt-julia'
@@ -26,6 +27,7 @@
   'ctrl-j ctrl-m': 'julia-client:set-working-module'
   'ctrl-alt-j ctrl-alt-m': 'julia-client:reset-working-module'
   'ctrl-`': 'julia-client:toggle-console'
+  'ctrl-shift-c': 'julia-client:interrupt-julia'

 '.platform-win32 .ink-console,
  .platform-linux .ink-console':


allow non-space between commas for private and keyword

--- grammars/schelp.cson
+++ grammars/schelp.cson
@@ -101,7 +101,7 @@
   }

   {
-    'match': '\\b(?i:KEYWORD)::\\s*(\\w+,\\s*)* \\w+$'
+    'match': '\\b(?i:KEYWORD)::\\s*(\\w+,\\s*)*\\s*\\w+$'
     'name': 'keyword.other.keyword.schelp'
   }

@@ -113,7 +113,7 @@
 #METHOD TAGS
 # inside the method definition, we can recognize other tags: argument, returns, discussion,
   {
-      'match': '\\b(?i:PRIVATE)::\\s*(\\w+,\\s*)* \\w+$'
+      'match': '\\b(?i:PRIVATE)::\\s*(\\w+,\\s*)*\\s*\\w+$'
       'name': 'storage.type.private.schelp'
   }
   {


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/engine_fade.coffee
+++ src/engine_fade.coffee
@@ -15,7 +15,7 @@

   engine.fadeIn()
-  # => Sets the effect variables to their default state. This will the screen to fade to black over the next 30 frames.
+  # => Sets the effect variables to their default state. This will the screen to go from black to transparent over the next 30 frames.

   engine.fadeIn('blue', 50)
   # => This effect will start off blue and fade to transparent over 50 frames.
@@ -38,7 +38,7 @@

   engine.fadeOut()
-  # => Sets the effect variables to their default state. This will the screen to fade from black to transparent over the next 30 frames.
+  # => Sets the effect variables to their default state. This will the screen to fade from ransparent to black over the next 30 frames.

   engine.fadeOut('blue', 50)
   # => This effect will start off transparent and change to blue over 50 frames.


refactor(grunt): use single quotes for sass imports

--- grunt/wiredep.coffee
+++ grunt/wiredep.coffee
@@ -5,3 +5,12 @@
   sass:
     ignorePath: /^(\.\.\/)+/
     src: 'app/styles/main.scss'
+    fileTypes:
+      scss:
+        block: /(([ \t]*)\/\/\s*bower:*(\S*))(\n|\r|.)*?(\/\/\s*endbower)/gi
+        detect:
+          scss: /@import\s['"](.+scss)['"]/gi
+        replace:
+          scss: (filePath) ->
+            filePath = filePath.replace('.scss', '').replace('/_', '/')
+            '@import \'' + filePath + '\';'


Use index and mid where appropriate

Use index and mid where appropriate

--- src/echo.coffee
+++ src/echo.coffee
@@ -87,12 +87,13 @@

         stream = {
           id: id
+          mid: id
           index: index
           nice: nice_stream
           needs_ice_cred: true
         }

-        @streams[m[1]] = stream
+        @streams[index] = stream

         # send candidates when gathered
         # TODO: move to trickling ...
@@ -100,7 +101,7 @@
         gatheringDone = (stream) => (candidates) =>
           log stream.id + " gathering done"
           for candidate in candidates
-            @signaling.sendCandidate stream.id, stream.index, candidate + '\r\n'
+            @signaling.sendCandidate stream.mid, stream.index, candidate + '\r\n'

         nice_stream.on 'gatheringDone', gatheringDone stream

@@ -158,6 +159,9 @@

         rtp_types = rtp_types.concat(parseInt(type) for type in types.split(" "))

+      else if m = line.match(/a=mid:(.*)/)
+        stream.mid = m[1]
+
       else if m = line.match(/a=ice-ufrag:(.*)/)
         # replace and apply ufrag
         stream.ufrag = m[1]
@@ -214,7 +218,7 @@
     if candidate.indexOf("a=") != 0
       candidate = "a=" + candidate

-    @streams[id]?.nice?.addRemoteIceCandidate candidate
+    @streams[index]?.nice?.addRemoteIceCandidate candidate

   close: () ->
     console.log 'closing echo'


fix route name

fix route name

--- services/web/app/coffee/router.coffee
+++ services/web/app/coffee/router.coffee
@@ -201,7 +201,7 @@
 		webRouter.get  "/project/:Project_id/doc/:doc_id/diff", AuthorizationMiddlewear.ensureUserCanReadProject, HistoryController.selectHistoryApi, HistoryController.proxyToHistoryApi
 		webRouter.get  "/project/:Project_id/diff", AuthorizationMiddlewear.ensureUserCanReadProject, HistoryController.selectHistoryApi, HistoryController.proxyToHistoryApiAndInjectUserDetails
 		webRouter.post "/project/:Project_id/doc/:doc_id/version/:version_id/restore", AuthorizationMiddlewear.ensureUserCanReadProject, HistoryController.selectHistoryApi, HistoryController.proxyToHistoryApi
-		privateApiRouter.post "/project/:Project_id/history/resync", AuthenticationController.httpAuth, HistoryController.resyncProject
+		privateApiRouter.post "/project/:Project_id/history/resync", AuthenticationController.httpAuth, HistoryController.resyncProjectHistory

 		webRouter.get  '/Project/:Project_id/download/zip', AuthorizationMiddlewear.ensureUserCanReadProject, ProjectDownloadsController.downloadProject
 		webRouter.get  '/project/download/zip', AuthorizationMiddlewear.ensureUserCanReadMultipleProjects, ProjectDownloadsController.downloadMultipleProjects


Move digest methods into metadata section

--- src/file.coffee
+++ src/file.coffee
@@ -114,6 +114,15 @@
   exists: ->
     fs.existsSync(@getPath())

+  # Public: Get the SHA-1 digest of this file
+  #
+  # Returns a {String}.
+  getDigest: ->
+    @digest ? @setDigest(@readSync())
+
+  setDigest: (contents) ->
+    @digest = crypto.createHash('sha1').update(contents ? '').digest('hex')
+
   # Public: Returns the {String} path for the file.
   getPath: -> @path

@@ -138,15 +147,6 @@
   getParent: ->
     Directory ?= require './directory'
     new Directory(path.dirname @path)
-
-  # Public: Get the SHA-1 digest of this file
-  #
-  # Returns a {String}.
-  getDigest: ->
-    @digest ? @setDigest(@readSync())
-
-  setDigest: (contents) ->
-    @digest = crypto.createHash('sha1').update(contents ? '').digest('hex')

   ###
   Section: Reading and Writing


updated transactions query for better filtering

--- src/api/transactions.coffee
+++ src/api/transactions.coffee
@@ -6,58 +6,29 @@
 # Retrieves the list of transactions
 ###
 exports.getTransactions = `function *getTransactions() {
-	var filtersObject = {};
-	filterSkip = 0;
-	filterPage = 0;
-	filterLimit = 10;
+	var filtersObject = this.request.query;

+	//construct date range filter option
+	if( filtersObject.startDate && filtersObject.endDate ){
+		filtersObject['request.timestamp'] = { $gte: filtersObject.startDate, $lt: filtersObject.endDate }

-	/*------------ Manipulate Request Parameters into filter object --------------*/
-	var filterVariablesUrl = this.request.url.replace("/transactions?", "");
-	filterVariables = filterVariablesUrl.split('&');
+		//remove startDate/endDate from objects filter (Not part of filtering and will break filter)
+		delete filtersObject.startDate;
+		delete filtersObject.endDate;
+	}

-	var urlParams = {};
-	for (i=0; i<filterVariables.length; i++) {
-		filterVariable = filterVariables[i].split('=');
+	//get limit and page values
+	var filterLimit = filtersObject.filterLimit;
+	var filterPage = filtersObject.filterPage;

-		//filterVariable[0] is the key to filter
-		//filterVariable[1] is the value to filter by
-		urlParams[filterVariable[0]] = filterVariable[1];
-	}
+	//remove limit/page values from filtersObject (Not apart of filtering and will break filter if present)
+	delete filtersObject.filterLimit;
+	delete filtersObject.filterPage;

-	if(urlParams.filterStatus){
-		filtersObject['status'] = urlParams.filterStatus;
-	}
+	//determine skip amount
+	var filterSkip = filterPage*filterLimit;

-	if(urlParams.filterEndpoint){
-		filtersObject['endpoint'] = urlParams.filterEndpoint;
-	}
-
-	if(urlParams.filterDateStart && urlParams.filterDateEnd){
-
-		startDate = new Date( urlParams.filterDateStart );
-		startDate = startDate.toISOString();
-
-		endDate = new Date( urlParams.filterDateEnd );
-		endDate.setDate(endDate.getDate() + 1);
-		endDate = endDate.toISOString();
-
-		filtersObject['request.timestamp'] = { $gte: startDate, $lt: endDate };
-	}
-
-	if(urlParams.filterShowPage){
-		filterPage = urlParams.filterShowPage;
-	}
-
-	if(urlParams.filterShowLimit){
-		filterLimit = urlParams.filterShowLimit;
-	}
-
-	filterSkip = filterPage*filterLimit;
-	/*------------ Manipulate Request Parameters into filter object --------------*/
-
-
-	try {
+	try {
 		this.body = yield transactions.Transaction.find(filtersObject).skip(filterSkip).limit(filterLimit).sort({ 'request.timestamp': -1 }).exec();
 	}catch (e){
 		this.message = e.message;


array_extensions

--- src/array_extensions.coffee
+++ src/array_extensions.coffee
@@ -191,8 +191,7 @@
 @param {Object} [context] Optional context parameter to be
 used as `this` when calling the iterator function.

-@type Array
-@returns this to enable method chaining.
+@returns {Array} this to enable method chaining.
 ###
 Array::each = (iterator, context) ->
   if this.forEach


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/array_extensions.coffee
+++ src/array_extensions.coffee
@@ -386,3 +386,12 @@
   this.inject 0, (sum, n) ->
     sum + n

+Array::zip = (args...) ->
+  this.map (element, index) ->
+    output = args.map (arr) ->
+      arr[index]
+
+    output.unshift(element)
+
+    return output
+


fix(account-store): Update focused perspective after triggering

- This prevents account store changes from propagating in the incorrect
  order

--- src/flux/stores/account-store.coffee
+++ src/flux/stores/account-store.coffee
@@ -75,8 +75,8 @@
       ipc = require('electron').ipcRenderer
       ipc.send('command', 'application:reset-config-and-relaunch')
     else
+      @trigger()
       Actions.focusDefaultMailboxPerspectiveForAccounts(@_accounts)
-      @trigger()

   _onReorderAccount: (id, newIdx) =>
     existingIdx = _.findIndex @_accounts, (a) -> a.id is id


add syntax sugar translatePoint(), scalePoint()

--- src/geom/Matrix.coffee
+++ src/geom/Matrix.coffee
@@ -43,8 +43,8 @@

   # ## apply(matrix:*Matrix*):*void*
   # Applies the properties of specified *Matrix* object to this object.
-  apply: (matrix)->
-    @_apply matrix.xx, matrix.xy, matrix.yx, matrix.yy, matrix.ox, matrix.oy
+  apply: ({xx, xy, yx, yy, ox, oy})->
+    @_apply xx, xy, yx, yy, ox, oy
   _apply: (xx, xy, yx, yy, ox, oy)->
     @xx = xx
     @xy = xy
@@ -65,8 +65,8 @@
   #     |xx yx ox||@xx @yx @ox|   |xx*@xx+yx*@xy xx*@yx+yx*@yy xx*@ox+yx*@oy+ox|
   #     |xy yy oy||@xy @yy @oy| = |xy*@xx+yy*@xy xy*@yx+yy*@yy xy*@ox+yy*@oy+oy|
   #     |0  0  1 ||0   0   1  |   |0             0             1               |
-  concat: (matrix)->
-    @_concat matrix.xx, matrix.xy, matrix.yx, matrix.yy, matrix.ox, matrix.oy
+  concat: ({xx, xy, yx, yy, ox, oy})->
+    @_concat xx, xy, yx, yy, ox, oy
   _concat:(xx, xy, yx, yy, ox, oy)->
     _xx = @xx
     _xy = @xy
@@ -87,10 +87,16 @@
   translate: (tx, ty)->
     @_concat 1, 0, 0, 1, tx, ty

+  translatePoint: ({x, y})->
+    @translate x, y
+
   # ## scale(sx:*Number*, sy:*Number*):*Matrix*
   # Applies a scaling transformation to this object.
   scale: (sx, sy)->
     @_concat sx, 0, 0, sy, 0, 0
+
+  scalePoint: ({x, y})->
+    @scale x, y

   # ## rotate(angle:*Number*):*Matrix*
   # Applies a rotation transformation to this object.
@@ -127,11 +133,11 @@
   #     |@xx @yx @ox||1 0 pt.x|   |@xx @yx @xx*pt.x+@yx*pt.y+@ox|
   #     |@xy @yy @oy||0 1 pt.y| = |@xy @yy @xy*pt.x+@yy*pt.y+@oy|
   #     |0   0   1  ||0 0 1   |   |0   0   1                    |
-  transformPoint: (pt)->
-    new Point @xx * pt.x + @yx * pt.y + @ox, @xy * pt.x + @yy * pt.y + @oy
+  transformPoint: ({x, y})->
+    new Point @xx * x + @yx * y + @ox, @xy * x + @yy * y + @oy

-  deltaTransformPoint: (pt)->
-    new Point @xx * pt.x + @yx * pt.y, @xy * pt.x + @yy * pt.y
+  deltaTransformPoint: ({x, y})->
+    new Point @xx * x + @yx * y, @xy * x + @yy * y

   #     |1 0 tx||sx 0  0||c -s 0|   |sx 0  tx||c -s 0|   |sx*c -sx*s tx|
   #     |0 1 ty||0  sy 0||s c  0| = |0  sy ty||s c  0| = |sy*s sy*c  ty|


stage scene improvement

stage scene improvement

--- src/game.coffee
+++ src/game.coffee
@@ -21,6 +21,13 @@
     }

   kick_off: () -> @switch_scene('welcome')
+
+  next_stage: () ->
+    @configs['current_stage'] = (@configs['current_stage'] + 1
+      + @configs['stages']) % @configs['stages']
+    if @configs['current_stage'] == 0
+      @configs['current_stage'] = @configs['stages']
+    @configs['current_stage']

   reset: () ->
     _.each @scenes, (scene) -> scene.stop()
@@ -265,7 +272,6 @@
 class StageScene extends Scene
   constructor: (@game) ->
     super(@game)
-    @max_stage = @game.get_config('stages')
     @init_stage_nodes()

   start: () ->
@@ -283,22 +289,18 @@
       switch event.which
         when 37, 38
           # UP, LEFT
-          @adjust_stage(1)
+          @current_stage = @game.prev_stage()
+          @update_stage_label()
         when 39, 40
           # RIGHT, DOWN
-          @adjust_stage(-1)
+          @current_stage = @game.next_stage()
+          @update_stage_label()
         when 13
           # ENTER
           @game.switch_scene('game')

   disable_stage_control: () ->
     $(document).unbind "keydown"
-
-  adjust_stage: (offset) ->
-    @current_stage = (@current_stage + offset + @max_stage) % @max_stage
-    @current_stage = @max_stage if @current_stage == 0
-    @game.set_config('current_stage', @current_stage)
-    @update_stage_label()

   init_stage_nodes: () ->
     # bg
@@ -634,16 +636,18 @@
     @enemy_win() if (@remain_user_p1_lives == 0 and @remain_user_p2_lives == 0)

   user_win: () ->
-    console.log "user win!"
-    setTimeout((() => @game.switch_scene('report')), 5000)
+    # report
+    setTimeout((() =>
+      @game.next_stage()
+      @game.switch_scene('report')
+    ), 5000)

   enemy_win: () ->
     # hi score or
     # welcome
-    console.log "enemy win!"
-    # setTimeout(() =>
-    #   @game.switch_scene('welcome')
-    # , 10000)
+    setTimeout(() =>
+      @game.switch_scene('welcome')
+    , 10000)

   born_tanks: (tank) ->
     if tank instanceof UserP1Tank


factor our create_level

factor our create_level

--- src/game.coffee
+++ src/game.coffee
@@ -37,17 +37,20 @@
   game_cache[name] = result
   result

+create_level = (game_dict, level) ->
+  level_dict = game_dict.levels[level]
+  level_dict.level_index = level
+  level_dict.level_count = game_dict.levels.length
+  level_dict.game = game_dict.name
+  level_dict
+
 exports.game = (rx, query) ->
   game_dict = find_game query.name
   level = query.level or 0
   my.assert game_dict.levels and _.isArray(game_dict.levels)
   world = god(rx, game_dict)

-  level_dict = game_dict.levels[level]
-  level_dict.level_index = level
-  level_dict.level_count = game_dict.levels.length
-  level_dict.game = game_dict.name
-
+  level_dict = create_level(game_dict, level)
   level_world = world.add_child level_dict
   for child in layout
     level_world.add_child child(level_dict)


create game world before adding children

--- src/game.coffee
+++ src/game.coffee
@@ -41,12 +41,18 @@
   game_dict = find_game query.name
   level = query.level or 0
   my.assert game_dict.levels and _.isArray(game_dict.levels)
+  world = god(rx, game_dict)
+
   level_dict = game_dict.levels[level]
   level_dict.level_index = level
   level_dict.level_count = game_dict.levels.length
   level_dict.game = game_dict.name
-  game_dict[my.key.children] = [level_dict]
+
   level_dict._CHILDREN = []
   for child in layout
     level_dict._CHILDREN.push child(level_dict)
-  world = god(rx, game_dict)
+
+  level_world = world.add_child level_dict
+  world.send(my.key.setup)
+
+  world