Rename LEVEL to level

Rename LEVEL to level

--- app/views/home-page-view.coffee
+++ app/views/home-page-view.coffee
@@ -24,8 +24,8 @@
     mediator.PlayWithSounds = confirm("Load Sounds?")

     # the first level
-    LEVEL = 'level1'
-    mediator.levels[LEVEL] = new Level()
+    level = 'level1'
+    mediator.levels[level] = new Level()

     @physicsManager = new PhysicsManager()
     @soundManager = new SoundManager() if mediator.PlayWithSounds
@@ -33,31 +33,31 @@

     @EntitySpawnManager = new EntitySpawnManager()

-    @loadLevel(LEVEL)
+    @loadLevel(level)

-    @subscribeEvent 'mapRendered:'+LEVEL, =>
-      @publishEvent 'levelChangeTo:'+LEVEL
+    @subscribeEvent 'mapRendered:'+level, =>
+      @publishEvent 'levelChangeTo:'+level

-  loadLevel: (LEVEL) =>
-    map = new TILEDMap("level":LEVEL)
-    mediator.levels[LEVEL].gMap = map
-    mediator.levels[LEVEL].gMap.load(LEVEL)
+  loadLevel: (level) =>
+    map = new TILEDMap("level":level)
+    mediator.levels[level].gMap = map
+    mediator.levels[level].gMap.load(level)

     if mediator.PlayWithSounds
-      @soundManager.load(LEVEL)
+      @soundManager.load(level)

-    @subscribeEvent 'levelChangeTo:'+LEVEL, =>
-      @setup(LEVEL)
+    @subscribeEvent 'levelChangeTo:'+level, =>
+      @setup(level)

-  setup: (LEVEL) =>
-    mediator.activeLevel = LEVEL
+  setup: (level) =>
+    mediator.activeLevel = level
     mediator.entities = []
     @physicsManager.setup()
     @EntitySpawnManager.initialSpawn()
     if mediator.PlayWithSounds
-      @subscribeEvent 'soundsLoaded:'+LEVEL, =>
-        @soundManager.playSound(LEVEL+'theme',mediator.levels[LEVEL].soundList, 1, true)
+      @subscribeEvent 'soundsLoaded:'+level, =>
+        @soundManager.playSound(level+'theme',mediator.levels[level].soundList, 1, true)
         @soundManager.startBackgroundSounds()
         @soundManager.updateBackgroundSounds(mediator.player.position)


Test: check typeof GithubUserApiService

Test: check typeof GithubUserApiService

--- test/spec/services/githubuserapi.coffee
+++ test/spec/services/githubuserapi.coffee
@@ -1,6 +1,6 @@
 'use strict'

-describe 'Service: GithubUserApi', ->
+describe 'Service: GithubUserApiService', ->

   # load the service's module
   beforeEach module 'arashike-blog'
@@ -10,7 +10,6 @@
   beforeEach inject (_GithubUserApiService_) ->
     GithubUserApiService = _GithubUserApiService_

-  it 'should do something', ->
-    expect !!GithubUserApiService
-      .to
-      .equal true
+  it 'should exist', ->
+    expect GithubUserApiService
+      .to.be.a 'function'


tools: update npm unit tests

tools: update npm unit tests

--- packages/tools/test/npm.coffee
+++ packages/tools/test/npm.coffee
@@ -15,25 +15,23 @@
       .tools.npm
         global: true
       , (err, {status}) ->
-        status.should.be.false() unless err
+        err.message.should.eql "should have required property '.name'"
       .promise()

-    they 'is valid', ({ssh}) ->
+    they 'name is valid', ({ssh}) ->
       nikita
         ssh: ssh
       .tools.npm
-        name: 'coffeescripts'
+        name: 'coffeescript'
       , ({options}) ->
-        options.name.should.be.an.Array()
-        options.global.should.be.Boolean()
-        options.upgrade.should.be.Boolean()
+        options.name.should.eql 'coffeescript'
       .promise()

     they 'use defaults', ({ssh}) ->
       nikita
         ssh: ssh
       .tools.npm
-        name: 'coffeescripts'
+        name: 'coffeescript'
       , ({options}) ->
         options.global.should.be.false()
         options.upgrade.should.be.false()


make sure objectSignature works with arrays

--- test/utils/object_signature_spec.coffee
+++ test/utils/object_signature_spec.coffee
@@ -26,3 +26,8 @@
     obj2.d = 5
     sig2 = objectSignature(obj2)
     expect( sig ).to.equal(sig2)
+
+  it "works with arrays", ->
+    sig1 = objectSignature(['a','b','c'])
+    sig2 = objectSignature(['a','b','c'])
+    expect( sig1 ).to.equal(sig2)


updated CanvasElement to use translateXY

--- src/art/engine/core/canvas_element.coffee
+++ src/art/engine/core/canvas_element.coffee
@@ -233,8 +233,8 @@
     _updateCanvasToDocumentMatricies: ->
       {left, top} = domElementOffset @_canvas
       @_canvasDocumentOffset = point left, top
-      @_elementToDocumentMatrix = Matrix.scale(1/@_devicePixelsPerPoint).translate left, top
-      @_documentToElementMatrix = Matrix.translate(-left, -top).scale @_devicePixelsPerPoint
+      @_elementToDocumentMatrix = Matrix.scale(1/@_devicePixelsPerPoint).translateXY left, top
+      @_documentToElementMatrix = Matrix.translateXY(-left, -top).scale @_devicePixelsPerPoint
       @_parentToElementMatrix = null
       @setElementToParentMatrix @_elementToAbsMatrix = Matrix.scale @_devicePixelsPerPoint


Add error notification for unparsed SyncTeX results

Fixes #114

--- lib/locator.coffee
+++ lib/locator.coffee
@@ -25,6 +25,11 @@
         )
         return
       record = @parseResult(stdout)
+      if Object.keys(record).length = 0
+        @latex.logger.processError(
+          'Failed SyncTeX', "Unable to parse output:\n\n\n #{stdout}"
+        )
+        return
       @latex.viewer.synctex(record)
     )


Setting tag attributes as properties fixed

--- attrsToSet.coffee
+++ attrsToSet.coffee
@@ -29,10 +29,10 @@
 		# set current attributes
 		for attr of @attrs
 			if node._attrs[attr]?
-				clone.setAttribute attr, null
+				this.setAttribute attr, null

 		# listen on changes
-		node.onAttrsChange @setAttribute, clone
+		node.onAttrsChange @setAttribute, this

 		`//<development>`
 		if @constructor is AttrsToSet


remove boomerang

remove boomerang

--- app/assets/javascripts/revised/components/ad_displayer.coffee
+++ app/assets/javascripts/revised/components/ad_displayer.coffee
@@ -16,7 +16,7 @@
   photoAd: (unit) ->
     $('.content-nav-group:last').addClass('additional-ad-space')
     console.log @geolocatedAd()
-    $("##{unit}").html([@geolocatedAd(), boomerang_ad].join('')).addClass('rendered-ad photo-ad')
+    $("##{unit}").html([@geolocatedAd()].join('')).addClass('rendered-ad photo-ad')

   geolocatedAd: ->
     location = localStorage.getItem('location')


Make bike deletes redirect and flash (#1195)

* Update edit_remove.coffee

* smoother

--- app/assets/javascripts/revised/pages/bikes/edit_remove.coffee
+++ app/assets/javascripts/revised/pages/bikes/edit_remove.coffee
@@ -17,6 +17,7 @@
   requestDeleteRequestCallback: (data, success) ->
     if success
       msg = "Your bike's been deleted!"
+      window.BikeIndexAlerts.add('info', msg, () -> window.location.href = "/user_home")
     else
       msg = "Oh no! Something went wrong and we couldn't send the delete request."
       window.BikeIndexAlerts.add('error', msg)


Update accessibility scripts, set up only not real buttons

--- app/assets/javascripts/uploadcare/widget/accessibility.coffee
+++ app/assets/javascripts/uploadcare/widget/accessibility.coffee
@@ -4,10 +4,8 @@
 } = uploadcare

 fakeButtons = [
-  '.uploadcare--link',
-  '.uploadcare--widget__button',
   '.uploadcare--menu__item',
-  '.uploadcare--button',
+  '.uploadcare--file__content',
 ].join(', ')

 mouseFocusedClass = 'uploadcare--mouse-focused'


Allow Default Initialization to Work With Single-Letter Property Names

--- src/coffee/experimental-controller.coffee
+++ src/coffee/experimental-controller.coffee
@@ -9,7 +9,7 @@
 # values.
 instantiate = (proto) ->
   instance = Object.create proto
-  for key, value of proto when /^default\w{2}/.test(key)
+  for key, value of proto when /^default\w/.test(key)
     [_, first, rest] = /^default(\w)(\w*)/.exec(key)
     newName = first.toLowerCase() + rest
     instance[newName] = value


test: remove test exclusivity

test: remove test exclusivity

--- test/api.sync.coffee
+++ test/api.sync.coffee
@@ -23,7 +23,7 @@
       'name 2': {'field_1': 'name 2', 'field_2': 'value 2'}
     }

-  it.only 'catch errors', ->
+  it 'catch errors', ->
     try
       parse 'A,B\nB\nC,K', trim: true
       throw Error 'Error not catched'


changed: description of Arma 3 AppData Folder config

--- lib/rptfile.coffee
+++ lib/rptfile.coffee
@@ -9,7 +9,7 @@
   config:
     appDataFolder:
       title: "Arma 3 AppData Folder",
-      description: "Location of the Arma 3 Application Data Folder. (Location of rpt-Files)",
+      description: "Location of the Arma 3 Application Data Folder (location of RPT files)",
       type: "string",
       default: "%localappdata%\\Arma 3\\"


Fix bug when calling commit before declare

--- lib/session.coffee
+++ lib/session.coffee
@@ -602,7 +602,7 @@
       return rpcRequest(sessionContext, null, callback)
     )

-  if not sessionContext.declarations[0].length
+  if sessionContext.doneProvisions[0] & 1
     return callback()

   if sessionContext.rpcCount >= 255 or


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/distance.coffee
+++ test/distance.coffee
@@ -14,7 +14,7 @@
     width: 10
     height: 10

-
+  player.include Distance

   enemy = GameObject
     x: 110


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/distance.coffee
+++ test/distance.coffee
@@ -15,6 +15,7 @@
     height: 10

   enemy = GameObject
-
+    x: 110
+    y:

 module()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/drawable.coffee
+++ test/drawable.coffee
@@ -355,7 +355,7 @@
          "complete":"Idle1",
          "interruptible":false,
          "speed":"110",
-         "tranform":[{hflip: true, vflip: true}],
+         "transform":[{hflip: true, vflip: true}],
          "triggers": {
            "0":["whiteParticles"],
            "4":["blueParticles","greenParticles"],
@@ -398,13 +398,11 @@

 test "should update transform to include xflip or vflip when present", ->
   obj = GameObject
-    data: animationData
-    includedModules: ["Animated"]
+    hflip: true
+
+  log obj.I.hflip

   obj.update()
-  obj.update()
-
-  log obj.I.transform

   equals obj.I.transform, Matrix.HORIZONTAL_FLIP


tween with arbitrary easing function

tween with arbitrary easing function

--- test/drawable.coffee
+++ test/drawable.coffee
@@ -37,7 +37,7 @@
   transform = object.transform()

   equal transform.a 1
-
+  equal transform.

   object = GameObject
     scale: 2


Hold Angular's scrubby string-as-error hand

--- packages/shrub-angular/client/index.coffee
+++ packages/shrub-angular/client/index.coffee
@@ -67,7 +67,15 @@
             # Create a promise.
             promise = new Promise ->
               resolve = arguments[0]
-              reject = arguments[1]
+
+              # Angular is a scrub, and will reject promises with strings
+              # instead of fully-fledged Error instances. Hold its hand.
+              reject_ = arguments[1]
+              reject = (error) ->
+                error = new Error error if _.isString error
+                reject_ error
+
+              return

             # Angular depends on notify, which is a crap API. We'll hack
             # support for it in.


update lquip loading behavior

update lquip loading behavior

  - removes b64 image after full image loads

this commit should fix #80

--- assets/coffeescript/lqip.coffee
+++ assets/coffeescript/lqip.coffee
@@ -13,32 +13,12 @@
       # img_large_div.classList.add 'Background-img--full-wh', 'img-large'
       # img_large_div.style.backgroundImage = "url('"+img_large.src+"');"
       $('.img-large').css('background-image', 'url(' + img_large.src + ')');
-      img_large_div.classList.add 'img-loaded'
+      img_large_div.classList.add('img-loaded')
+
+      # remove b64 bkg image only if img_large is fully loaded
+  img_large_div.addEventListener "transitionend", (e) =>
+    img_small.style.removeProperty('background-image')

-    # placeholder.appendChild(img_large_div)
-
-
-
-# window.onload = function() {
-
-#   var placeholder = document.querySelector('.placeholder'),
-#       small = placeholder.querySelector('.img-small')
-
-#   // 1: load small image and show it
-#   var img = new Image();
-#   img.src = small.src;
-#   img.onload = function () {
-#    small.classList.add('loaded');
-#   };
-
-#   // 2: load large image
-#   var imgLarge = new Image();
-#   imgLarge.src = placeholder.dataset.large;
-#   imgLarge.onload = function () {
-#     imgLarge.classList.add('loaded');
-#   };
-#   placeholder.appendChild(imgLarge);
-# }

 $(document).ready ->
   blur_up()


fix CSS animation fill-style -> fill-mode

--- scripts/jquery/css_animation.coffee
+++ scripts/jquery/css_animation.coffee
@@ -4,7 +4,7 @@
   direction: 'webkitAnimationDirection'
   duration: 'webkitAnimationDuration'
   easing: 'webkitAnimationTimingFunction'
-  fillStyle: 'webkitAnimationFillStyle'
+  fillMode: 'webkitAnimationFillMode'
   iterations: 'webkitAnimationIterationCount'

 jQuery.cssAnimationEvents =


Update in-game reviewing to match other reviewing

--- picoCTF-web/web/coffee/game_support.coffee
+++ picoCTF-web/web/coffee/game_support.coffee
@@ -1,8 +1,8 @@
 @renderProblemReview = _.template($("#problem-review-template").remove().text())

 @ratingMetrics = ["Difficulty", "Enjoyment", "Educational Value"]
-@ratingQuestion = {"Difficulty": "How difficult is this problem?", "Enjoyment": "Did you enjoy this problem?", "Educational Value": "How much did you learn by doing this problem?"}
-@ratingChoices = {"Difficulty": ["Very Easy", "", "Average", "", "Too Hard"], "Enjoyment": ["Hated it!", "", "Neutral", "", "Loved it!"], "Educational Value": ["Learned Nothing","", "Some New Things", "", "A Lot"]}
+@ratingQuestion = {"Difficulty": "How difficult is this problem?", "Enjoyment": "Did you enjoy this problem?", "Educational Value": "How much did you learn while solving this problem?"}
+@ratingChoices = {"Difficulty": ["Too easy", "", "A bit challenging", "", "Very hard"], "Enjoyment": ["Hated it!", "", "It was okay.", "", "Loved it!"], "Educational Value": ["Nothing at all","", "Something useful", "", "Learned a lot!"]}

 @timeValues = ["5 minutes or less", "10 minutes", "20 minutes", "40 minutes", "1 hour", "2 hours", "3 hours", "4 hours", "5 hours", "6 hours", "8 hours", "10 hours", "15 hours", "20 hours", "30 hours", "40 hours or more"]


GameObject.Effect

GameObject.Effect

--- test/tweening.coffee
+++ test/tweening.coffee
@@ -11,7 +11,7 @@
     o.update(1)
     o.trigger "afterUpdate", 1

-    equals o.I.x, i +
+    equals o.I.x, i + 1

 # Clear out the module
 module()


Allow the pointers to overlap more

Allow the pointers to overlap more

--- src/angular-slider.coffee
+++ src/angular-slider.coffee
@@ -196,7 +196,7 @@
                         #In testing, situations where the pointers cross have sometimes created odd/buggy behavoir
                         #To prevent this, make it so that the pointers may not get closer than 8% of the slider's total width
                         if range
-                            bufferSize = valueRange * .08
+                            bufferSize = valueRange * .03
                             maxLowValue = parseInt(scope[refHigh]) - bufferSize
                             minHighValue = parseInt(scope[refLow]) + bufferSize
                             if ref is refLow


fixed follow button

fixed follow button

--- app/assets/javascripts/user.coffee
+++ app/assets/javascripts/user.coffee
@@ -37,9 +37,9 @@
         switch action
           when 'follow'
             btn[0].dataset.action = 'unfollow'
-            btn.attr 'class', 'btn btn-default btn-block'
+            btn.attr 'class', 'btn btn-default btn-block profile--follow-btn'
             btn.html 'Unfollow'
           when 'unfollow'
             btn[0].dataset.action = 'follow'
-            btn.attr 'class', 'btn btn-primary btn-block'
+            btn.attr 'class', 'btn btn-primary btn-block profile--follow-btn'
             btn.html 'Follow'


Rename the crossfilter.

Rename the crossfilter.

--- app/javascripts/correlation.coffee
+++ app/javascripts/correlation.coffee
@@ -650,14 +650,11 @@
           padding = config.padding
           axes = config.axes

-          # Set up the crossfilter.
-          # TODO - what is ndx? Rename and describe.
-          #   'ndx' conventionally signifies an integer index.
-          #   That is not what ndx is here.
-          ndx = crossfilter(data)
+          # Construct the multi-dimensional crossfilter.
+          xFilter = crossfilter(data)

           # The patient/visit dimension.
-          dim = ndx.dimension (obj) ->
+          dim = xFilter.dimension (obj) ->
             [
               obj.subject.number
               obj.visit.number
@@ -745,9 +742,9 @@
             yAxis = axes[i].y

             # Set up the dimension based on the X and Y axis user selections.
-            # TODO - is d here the chart data object? If so, then comment and
-            # rename to obj here and below.
-            dim = ndx.dimension (d) ->
+# TODO - is d here the chart data object? If so, then comment and
+# rename to obj here and below.
+            dim = xFilter.dimension (d) ->
               [
                 d[xAxis]
                 d[yAxis]


fixes log features filter

fixes log features filter

--- app/js/filters/log_features.coffee
+++ app/js/filters/log_features.coffee
@@ -17,7 +17,7 @@
         text

       when 'sea'
-        text = 'Binnen'
+        text = 'See'
         text += ', unter Motor'     if log.engine
         text += ', mit Familie'     if log.family
         text += ', starke Strömung' if log.current


Fix wrong function call

Fix wrong function call

--- app/js/loader/loader_common.coffee
+++ app/js/loader/loader_common.coffee
@@ -68,7 +68,6 @@
     root.perf.add_existing_timing_event 'before_jail', load_time_before_jail
     root.perf.add_timing_event 'after_jail'

-
     root.initializers.forEach( (f) -> f())

     root.core_loaded_promise
@@ -94,7 +93,7 @@
     if document.body
       whenHasBody()
     else
-      setTimeout i, tryToInit(i+1)
+      setTimeout tryToInit(i+1), i

   tryToInit(1)()


Added new level 'QuickSort the Spiral'

Added new level 'QuickSort the Spiral'

--- app/views/play/MainPlayView.coffee
+++ app/views/play/MainPlayView.coffee
@@ -251,6 +251,13 @@
         description: 'Learn cluster analysis while leading armies into battle! - by Alexandru Caciulescu'
       }
       {
+        name: 'QuickSort the Spiral'
+        difficulty: 3
+        id: 'quicksort-the-spiral'
+        image: '/file/db/level/525ef8ef06e1ab0962000003/commanding_followers_icon.png'
+        description: 'Learn Quicksort while sorting a spiral of ogres! - by Alexandru Caciulescu'
+      }
+      {
         name: 'Find the Spy'
         difficulty: 2
         id: 'find-the-spy'


Update WorldMapView.coffee

Update WorldMapView.coffee

--- app/views/play/WorldMapView.coffee
+++ app/views/play/WorldMapView.coffee
@@ -781,7 +781,7 @@
   #{id: 'beginner', name: 'Beginner Campaign', description: '... in which you learn the wizardry of programming.', levels: tutorials, color: "rgb(255, 80, 60)"}
   #{id: 'multiplayer', name: 'Multiplayer Arenas', description: '... in which you code head-to-head against other players.', levels: arenas, color: "rgb(80, 5, 60)"}
   #{id: 'dev', name: 'Random Harder Levels', description: '... in which you learn the interface while doing something a little harder.', levels: experienced, color: "rgb(80, 60, 255)"}
-  #{id: 'classic' ,name: 'Classic Algorithms', description: '... in which you learn the most popular algorithms in Computer Science.', levels: classicAlgorithms, color: "rgb(110, 80, 120)"}
+  #{id: 'classic_algorithms' ,name: 'Classic Algorithms', description: '... in which you learn the most popular algorithms in Computer Science.', levels: classicAlgorithms, color: "rgb(110, 80, 120)"}
   #{id: 'player_created', name: 'Player-Created', description: '... in which you battle against the creativity of your fellow Artisan Wizards.', levels: playerCreated, color: "rgb(160, 160, 180)"}
   {id: 'beginner', name: 'Beginner Campaign', levels: hero, color: 'rgb(255, 80, 60)'}
 ]


multiple turtles

multiple turtles

--- src/config/turtles.coffee
+++ src/config/turtles.coffee
@@ -29,6 +29,8 @@
   name: (world, args) ->
     world.label()
   stroke: "green"
+  v_i: 1
+  v_j: 0
   angle: (world, args) ->
     # TODO: perform real triginometry
     value = 90*(1-world.get('v_i')) #0, 90, 180, 90, 0
@@ -61,5 +63,11 @@
       v_j: 1
       fill: "#88ff88"
     }
+    {
+      _LABEL: "yu"
+      i: 1.5
+      j: 3.5
+      fill: "#008800"
+    }
   ]
 }


Inline known value

Inline known value

--- core/app/backbone/views/react_install_extension_button.coffee
+++ core/app/backbone/views/react_install_extension_button.coffee
@@ -42,7 +42,7 @@
           _em [],
             'To install Factlink, drag this button to your bookmarks bar'
     else if @props.huge_button && extension_installed
-      _button ['button', extra_class,
+      _button ['button', 'button-huge',
         disabled: true],
         'Factlink already installed.'
     else if extension_installed && !@props.huge_button


fixing a bug for when adding in new homeworks

--- src/flux/task-plan.coffee
+++ src/flux/task-plan.coffee
@@ -130,8 +130,8 @@
     {tasking_plans} = @_getPlan(id)
     # do all the tasking_plans have the same date?
     dates = _.compact _.uniq _.map(tasking_plans, (plan) ->
-      date = TimeHelper.getMomentPreserveDate(plan[attr]).toDate()
-      if isNaN(date.getTime()) then 0 else date.getTime()
+      date = TimeHelper.getMomentPreserveDate(plan[attr]).toDate() if plan[attr]
+      if not date or isNaN(date.getTime()) then 0 else date.getTime()
     )
     if dates.length is 1 then new Date(_.first(dates)) else null


Fix import.

Fix import.

--- src/spec/producttypegenerator.spec.coffee
+++ src/spec/producttypegenerator.spec.coffee
@@ -1,11 +1,14 @@
-ProductTypeGenerator = require('../lib/producttypegenerator').ProductTypeGenerator
+ProductTypeGenerator = require('../main').ProductTypeGenerator

 describe 'ProductTypeGenerator', ->
   beforeEach ->
     @generator = new ProductTypeGenerator('foo')
+    console.log "test1 #{@generator}"

   it 'should initialize', ->
+    console.log "test2 #{@generator}"
     expect(@generator).toBeDefined()

   it 'should initialize with options', ->
+    console.log "test3 #{@generator}"
     expect(@generator._options).toBe 'foo'


All emissions will propagate out from the async layer.

--- src/git-repository.coffee
+++ src/git-repository.coffee
@@ -1,7 +1,7 @@
 {basename, join} = require 'path'

 _ = require 'underscore-plus'
-{Emitter, Disposable, CompositeDisposable} = require 'event-kit'
+{Disposable, CompositeDisposable} = require 'event-kit'
 fs = require 'fs-plus'
 GitRepositoryAsync = require './git-repository-async'
 GitUtils = require 'git-utils'
@@ -69,7 +69,6 @@
       null

   constructor: (path, options={}) ->
-    @emitter = new Emitter
     @subscriptions = new CompositeDisposable

     @repo = GitUtils.open(path)
@@ -107,11 +106,6 @@
   # This destroys any tasks and subscriptions and releases the underlying
   # libgit2 repository handle. This method is idempotent.
   destroy: ->
-    if @emitter?
-      @emitter.emit 'did-destroy'
-      @emitter.dispose()
-      @emitter = null
-
     if @statusTask?
       @statusTask.terminate()
       @statusTask = null
@@ -486,7 +480,4 @@

         resolve()

-        unless statusesUnchanged
-          @emitter.emit 'did-change-statuses'
-
     return Promise.all([asyncRefresh, syncRefresh])


specs for the ctrl-enter/cmd-enter piece.

--- public/spec/try-it-spec.coffee
+++ public/spec/try-it-spec.coffee
@@ -351,5 +351,25 @@

     it "shows some coffee", ->
       expect(templates.goCoffee).toHaveBeenCalled()
-
-
+
+  describe "hitting enter", ->
+    beforeEach ->
+      spyOn(window, "tryIt")
+
+    context "just enter", ->
+      beforeEach -> $(document.body).trigger({type: 'keydown', which: 13})
+
+      it "doesn't execute the specs", ->
+        expect(tryIt).not.toHaveBeenCalled()
+
+    context "cmd-enter", ->
+      beforeEach -> $(document.body).trigger({type: 'keydown', which: 13, metaKey: true})
+
+      it "executes the specs", ->
+        expect(tryIt).toHaveBeenCalled()
+
+    context "ctrl-enter", ->
+      beforeEach -> $(document.body).trigger({type: 'keydown', which: 13, ctrlKey: true})
+
+      it "executes the specs", ->
+        expect(tryIt).toHaveBeenCalled()


must be admin to create certificates

must be admin to create certificates

--- src/api/certificateAuthority.coffee
+++ src/api/certificateAuthority.coffee
@@ -4,8 +4,14 @@
 logger = require 'winston'
 utils = require "../utils"
 pem = require "pem"
+authorisation = require './authorisation'

 exports.generateCert = ->
+  # Must be admin
+  if authorisation.inGroup('admin', this.authenticated) is false
+    utils.logAndSetResponse this, 403, "User #{this.authenticated.email} is not an admin, API access to getServerKey by id denied.", 'info'
+    return
+
   options = this.request.body
   if options.type is 'server'
     logger.info 'Generating server cert'


Add capacity to inform a minHeight property

--- src/app/components/directive.coffee
+++ src/app/components/directive.coffee
@@ -19,9 +19,9 @@
           recurrFunc = (increment) ->
             currObject.height(currObject.height() + increment)

-          recurrFunc(10) while parentObject.height() >= parentObject.prop('scrollHeight')
+          recurrFunc(10) while currObject.height() < ngFillHeightOption.minHeight or parentObject.height() >= parentObject.prop('scrollHeight')

-          recurrFunc(-1) while parentObject.height() < parentObject.prop('scrollHeight')
+          recurrFunc(-1) while currObject.height() > ngFillHeightOption.minHeight and parentObject.height() < parentObject.prop('scrollHeight')

           return
       }


New platform specified files extensions added

--- cli/tasks/build/parse/index.coffee
+++ cli/tasks/build/parse/index.coffee
@@ -6,16 +6,21 @@

 PLATFORM_TYPES =
 	node:
+		node: true
 		server: true
 	browser:
+		browser: true
 		client: true
 	qt:
+		qt: true
 		client: true
 		native: true
 	android:
+		android: true
 		client: true
 		native: true
 	ios:
+		ios: true
 		client: true
 		native: true


Terminal: as webtermview instance is destroyed it  emits TerminalClosed event with vmName and sessionId

--- client/Terminal/webtermview.coffee
+++ client/Terminal/webtermview.coffee
@@ -209,6 +209,10 @@

   destroy: ->
     super
+    @emit "TerminalClosed",
+      vmName   : @getVMName()
+      sessionId: @getOptions().session
+
     @terminal.server?.terminate()

   updateSettings: ->


client: fix linting issues

client: fix linting issues

--- client/app/lib/util/cleanup.coffee
+++ client/app/lib/util/cleanup.coffee
@@ -13,13 +13,12 @@
   # 'ajs_group_id'     # font?
   # 'ajs_user_id'      # font?
   # 'koding-teams'     # set by koding client, should be moved to session storage.
-
 ]

 hostname = location.host
-subDomain = hostname.substring(hostname.indexOf("."), hostname.length)
+subDomain = hostname.substring(hostname.indexOf('.'), hostname.length)
 dotlessSubDomain = subDomain.substring(1, subDomain.length)
-subSubDomain = dotlessSubDomain.substring(dotlessSubDomain.indexOf("."), dotlessSubDomain.length)
+subSubDomain = dotlessSubDomain.substring(dotlessSubDomain.indexOf('.'), dotlessSubDomain.length)
 dotlessSubSubDomain = subSubDomain.substring(1, subSubDomain.length)

 perms =


Test/MyTeam add browser end function

Test/MyTeam add browser end function

--- client/test/lib/teams/teams.coffee
+++ client/test/lib/teams/teams.coffee
@@ -9,58 +9,58 @@

 module.exports =

-  # # Team Creation
-  createTeamWithInvalidEmail: (browser) ->
-    teamsHelpers.createTeam(browser, user, '', 'InvalidEmail')
+  # # # Team Creation
+  # createTeamWithInvalidEmail: (browser) ->
+  #   teamsHelpers.createTeam(browser, user, '', 'InvalidEmail')

-  createTeamWithInvalidTeamUrl: (browser) ->
-    teamsHelpers.createTeam(browser, user, '' , 'InvalidTeamUrl')
+  # createTeamWithInvalidTeamUrl: (browser) ->
+  #   teamsHelpers.createTeam(browser, user, '' , 'InvalidTeamUrl')

-  createTeamWithEmptyTeamUrl: (browser) ->
-    teamsHelpers.createTeam(browser, user, '' , 'EmptyTeamUrl')
+  # createTeamWithEmptyTeamUrl: (browser) ->
+  #   teamsHelpers.createTeam(browser, user, '' , 'EmptyTeamUrl')

-  createTeamWithUpperCaseTeamUrl: (browser) ->
-    teamsHelpers.createTeam(browser, user, '' , 'UpperCaseTeamUrl')
+  # createTeamWithUpperCaseTeamUrl: (browser) ->
+  #   teamsHelpers.createTeam(browser, user, '' , 'UpperCaseTeamUrl')

-  createTeamWithAlreadyUsedTeamUrl: (browser) ->
-    teamsHelpers.createTeam(browser, user, '' , 'AlreadyUsedTeamUrl')
+  # createTeamWithAlreadyUsedTeamUrl: (browser) ->
+  #   teamsHelpers.createTeam(browser, user, '' , 'AlreadyUsedTeamUrl')

-  # Create Account Steps in Team Creation
-  createAccountWithInvalidUserName: (browser) ->
-    user         = utils.getUser(yes)
-    teamsHelpers.createTeam(browser, user, '' , 'InvalidUserName')
+  # # Create Account Steps in Team Creation
+  # createAccountWithInvalidUserName: (browser) ->
+  #   user         = utils.getUser(yes)
+  #   teamsHelpers.createTeam(browser, user, '' , 'InvalidUserName')

-  createAccountWithSameDomainAndUserName: (browser) ->
-    user         = utils.getUser(yes)
-    teamsHelpers.createTeam(browser, user, '' , 'SameDomainAndUserName')
+  # createAccountWithSameDomainAndUserName: (browser) ->
+  #   user         = utils.getUser(yes)
+  #   teamsHelpers.createTeam(browser, user, '' , 'SameDomainAndUserName')

-  createAccountWithShortPassword: (browser) ->
-    user = utils.getUser(yes)
-    teamsHelpers.createTeam(browser, user, '' , 'ShortPassword')
+  # createAccountWithShortPassword: (browser) ->
+  #   user = utils.getUser(yes)
+  #   teamsHelpers.createTeam(browser, user, '' , 'ShortPassword')

   createAccountAlreadyRegisteredUserName: (browser) ->
     teamsHelpers.createTeam(browser, user, inviteLink)
     teamsHelpers.createTeam(browser, user, createLink, 'AlreadyRegisteredUserName')
-
+    browser.end()

   signInWithNotAllowedEmail: (browser) ->
     teamsHelpers.loginTeam browser, user, yes , 'NotAllowedEmail', ->
-      browser.pause 1
+      browser.end()

   signInWithInvalidUsername: (browser) ->
     teamsHelpers.loginTeam browser, user, yes, 'InvalidUserName', ->
-      browser.pause 1
+      browser.end()

   signInWithInvalidPassword: (browser) ->
     teamsHelpers.loginTeam browser, user, yes, 'InvalidPassword', ->
-      browser.pause 1
+      browser.end()

   loginTeam: (browser) ->
     teamsHelpers.loginTeam browser, user, no, ->
-      browser.pause 1
+      browser.end()

   seePreviouslyVisitedTeams: (browser) ->


koding router: temporarily lower this error to warning level

--- client/Main/kodingrouter.coffee
+++ client/Main/kodingrouter.coffee
@@ -143,6 +143,9 @@

     callback = =>
       KD.getSingleton("appManager").tell section, method, options ? models, (contentDisplay) =>
+        unless contentDisplay
+          console.warn 'no content display'
+          return
         routeWithoutParams = route.split('?')[0]
         @openRoutes[routeWithoutParams] = contentDisplay
         @openRoutesById[contentDisplay.id] = routeWithoutParams


payment: fix year valiation on cc form

payment: fix year valiation on cc form

--- client/Main/payment/form.coffee
+++ client/Main/payment/form.coffee
@@ -190,12 +190,12 @@
       validate           :
         rules            :
           checkMonthYear : (yearInput, event) =>
-            monthInput = @form.inputs.cardYear
+            monthInput = @form.inputs.cardMonth
             monthVal   = $.trim monthInput.getValue().replace(/-|\s/g, '')
             yearVal    = $.trim yearInput.getValue().replace(/-|\s/g, '')

             result     = Stripe.card.validateExpiry monthVal, yearVal
-            input.setValidationResult 'checkMonthYear', not result
+            yearInput.setValidationResult 'checkMonthYear', not result

         events           :
           checkMonthYear : 'blur'


correct password reset alert message

correct password reset alert message

--- client/code/view/sign-up.coffee
+++ client/code/view/sign-up.coffee
@@ -66,7 +66,7 @@
           @$el.prepend $div
           if jsonResponse.code == 'username-duplicate'
             # :todo: Add password reset link.
-            $div.append(""" Is that you? If we had a password reset link, we'd give it to you now.""")
+            $div.append(""" Is that you? We have emailed you a password reset link.""")
           else
             # Don't really know what the error is, so say something technical and geeky.
             $div.append("""<code>#{JSON.stringify jsonResponse}</code>""")


Multiple Promise helper method fixes.

Multiple Promise helper method fixes.

Fixed RTEs related previous changes in Deferred API.
Fixed typo - invalid fixed size Array initialization.
Fixed function scoping issue in Promise.reduce.

--- src/coffee/Deft/util/Promise.coffee
+++ src/coffee/Deft/util/Promise.coffee
@@ -11,14 +11,16 @@
 			if promiseOrValue instanceof Ext.ClassManager.get( 'Deft.util.Promise' ) or promiseOrValue instanceof Ext.ClassManager.get( 'Deft.util.Deferred' )
 				return promiseOrValue.then( callbacks )
 			else
-				return Ext.create( 'Deft.util.Deferred' ).resolve( promiseOrValue ).then( callbacks )
+				deferred = Ext.create( 'Deft.util.Deferred' )
+				deferred.resolve( promiseOrValue )
+				return deferred.then( callbacks )

 		###*
 		Returns a new {@link Deft.util.Promise} that will only resolve once all the specified `promisesOrValues` have resolved.
 		The resolution value will be an Array containing the resolution value of each of the `promisesOrValues`.
 		###
 		all: ( promisesOrValues, callbacks ) ->
-			results = new Array[ promisesOrValues.length ]
+			results = new Array( promisesOrValues.length )
 			promise = @reduce( promisesOrValues, @reduceIntoArray, results )

 			return @when( promise, callbacks )
@@ -61,7 +63,7 @@
 		###
 		map: ( promisesOrValues, mapFunction ) ->
 			# Since the map function may be asynchronous, get all invocations of it into flight ASAP.
-			results = new Array[ promisesOrValues.length ]
+			results = new Array( promisesOrValues.length )
 			for index, promiseOrValue in promisesOrValues
 				if index of promisesOrValues
 					results[ index ] = @when( promiseOrValue, mapFunction )
@@ -74,10 +76,11 @@
 		###
 		reduce: ( promisesOrValues, reduceFunction, initialValue ) ->
 			# Wrap the reduce function with one that handles promises and then delegates to it.
+			whenResolved = @when
 			reduceArguments = [
 				( previousValueOrPromise, currentValueOrPromise, currentIndex ) ->
-					return @when( previousValueOrPromise, ( previousValue ) ->
-						return @when( currentValueOrPromise, ( currentValue ) ->
+					return whenResolved( previousValueOrPromise, ( previousValue ) ->
+						return whenResolved( currentValueOrPromise, ( currentValue ) ->
 							return reduceFunction( previousValue, currentValue, currentIndex, promisesOrValues )
 						)
 					)
@@ -86,7 +89,9 @@
 			if ( arguments.length is 3 )
 				reduceArguments.push( initialValue )

-			return Ext.create( 'Deft.util.Deferred' ).resolve( @reduceArray.apply( promisesOrValues, reduceArguments ) ).promise
+			deferred = Ext.create( 'Deft.util.Deferred' )
+			deferred.resolve( @reduceArray.apply( promisesOrValues, reduceArguments ) )
+			return deferred.promise

 		###*
 		Internal reduce implementation - includes fallback when Array.reduce is not available.
@@ -124,7 +129,7 @@
 			while index < length
 				# Skip holes
 				if index of array
-					reduced = reduceFunc( reduced, array[ index ], index, array )
+					reduced = reduceFunction( reduced, array[ index ], index, array )
 				index++

 			return reduced


Collaboration: Fixed deleting file content in change object before syncing it to Drive.

--- client/ide/AppController.coffee
+++ client/ide/AppController.coffee
@@ -1012,7 +1012,8 @@
           string = @rtm.getFromModel path
           string.setText content  if string

-        delete context.file?.content?
+        if context.file?.content
+          delete context.file.content

       @changes.push change


chat: reload messages when leaving session

--- client/ide/AppController.coffee
+++ client/ide/AppController.coffee
@@ -1508,6 +1508,7 @@
       @broadcastMessages.push origin: KD.nick(), type: 'ParticipantWantsToLeave'
       @removeMachineNode()
       @modal.destroy()
+      KD.singletons.mainView.activitySidebar.emit 'ReloadMessagesRequested'
       KD.singletons.router.handleRoute '/IDE'


treat > 400 as errors

treat > 400 as errors

Sometimes when it's a CORS request xhr doesn't trigger the error for
unknown reasons

--- client/lanes/models/Sync.coffee
+++ client/lanes/models/Sync.coffee
@@ -52,7 +52,7 @@
             Lanes.Models.Sync.perform(method, options).then (reply) ->
                 handler(reply)
             , (err) ->
-                reply = { errors: { http: err.error.message } }
+                reply = { errors: { http:  err?.error?.message or 'unknown' } }
                 try
                     reply = JSON.parse(err.body) unless _.isEmpty(err?.body)
                 finally
@@ -83,14 +83,13 @@
         #     options.json = options.data
         options.headers ||= {}
         # options.json = true
-        options.headers['X_CSRF_TOKEN'] = Lanes.config.csrf_token
+        if Lanes.config.csrf_token
+            options.headers['X_CSRF_TOKEN'] = Lanes.config.csrf_token
         options.contentType = "application/json"

-        # Make the request, allowing the user to override any Ajax options.
-        # xhr = options.xhr = Lanes.$.ajax(_.extend(params, options))
         new _.Promise( (resolve, reject) ->
             options.xhr = Lanes.Vendor.xhr(options, (err, resp, body) ->
-                if err
+                if err or resp.statusCode >= 400
                     reject(error:err, body:options.xhr.responseText)
                 else
                     resolve(body)


Add threshold to check if room is at bottom

--- packages/rocketchat-ui/views/app/room.coffee
+++ packages/rocketchat-ui/views/app/room.coffee
@@ -547,8 +547,9 @@

 	messageBox = $('.messages-box')

-	template.isAtBottom = ->
-		if wrapper.scrollTop >= wrapper.scrollHeight - wrapper.clientHeight
+	template.isAtBottom = (scrollThreshold) ->
+		if not scrollThreshold? then scrollThreshold = 0
+		if wrapper.scrollTop + scrollThreshold >= wrapper.scrollHeight - wrapper.clientHeight
 			newMessage.className = "new-message not"
 			return true
 		return false
@@ -558,7 +559,7 @@
 		newMessage.className = "new-message not"

 	template.checkIfScrollIsAtBottom = ->
-		template.atBottom = template.isAtBottom()
+		template.atBottom = template.isAtBottom(100)
 		readMessage.enable()
 		readMessage.read()


fix base directive bug

fix base directive bug

--- src/compiler/directives/base.coffee
+++ src/compiler/directives/base.coffee
@@ -57,7 +57,7 @@
   # @return {HTMLString}
   content: (context={}) ->
     content = @contentString()
-    renderContentString(content,context)
+    @renderContentString(content,context)

   # Escapes a string to make it HTML safe. See note on `safe` about marking
   # a string as safe.


listview: fix the subview insert for 1 item listview

--- src/components/list/listview.coffee
+++ src/components/list/listview.coffee
@@ -113,7 +113,8 @@

     unless boxed

-      if isLastIndex then @addSubView item
+      if isFirstIndex     then @addSubView item, null, yes
+      else if isLastIndex then @addSubView item
       else addNeighborItem item, index

     else


Finishes tests for XDoTool controller

Finishes tests for XDoTool controller

The tests are cleaner - but I still think I have to dig into sinon some more.

--- test/unit/controllers/xdotool.test.coffee
+++ test/unit/controllers/xdotool.test.coffee
@@ -3,37 +3,76 @@
 XDoToolCtrl		= require '../../../src/controllers/xdotool'

 describe 'XDoToolCtrl', ->
-	xdotoolCtrl = xdotoolStub = xdotoolApi = commanderStub = commanderApi = req = resStub = resApi = null
+	xdotoolCtrl = xdotool = commander = req = res = null
 	beforeEach ->
-		xdotoolApi = moveRelative: (->), leftClick: (->), rightClick: (->), doubleClick: (->)
-		commanderApi = exec: ->
-		resApi = json: ->
+		xdotool = moveRelative: sinon.stub(), leftClick: sinon.stub(), rightClick: sinon.stub(), doubleClick: sinon.stub()
+		commander = exec: sinon.stub()
+		res = json: sinon.stub()
+		xdotoolCtrl = new XDoToolCtrl commander, xdotool

 	describe 'move mouse relative', ->
 		beforeEach ->
 			req = body: xrel: 1, yrel: -1
-			resStub = sinon.stub(resApi, 'json')
-			commanderStub = sinon.stub(commanderApi, 'exec')
-			xdotoolStub = sinon.stub(xdotoolApi, 'moveRelative').returns("xdotool cmd")
-			xdotoolCtrl = new XDoToolCtrl commanderApi, xdotoolApi
+			xdotool.moveRelative.returns('xdotool cmd')
+			xdotoolCtrl.moveRelative req, res

 		it 'should provide the correct arguments to the xdotool module', ->
-			xdotoolCtrl.moveRelative req, resApi
-			expect(xdotoolStub.calledOnce).to.eql true
-			expect(xdotoolStub.args[0][0]).to.eql 1
-			expect(xdotoolStub.args[0][1]).to.eql -1
+			expect(xdotool.moveRelative.calledOnce).to.be.true
+			expect(xdotool.moveRelative.calledWith(1, -1)).to.be.true

 		it 'should call the commander with the result of xdotool', ->
-			xdotoolCtrl.moveRelative req, resApi
-			expect(commanderStub.calledOnce).to.eql true
-			expect(commanderStub.args[0][0]).to.eql 'xdotool cmd'
+			expect(commander.exec.calledOnce).to.be.true
+			expect(commander.exec.calledWith('xdotool cmd')).to.be.true

 		it 'should respond as JSON with a 200 status', ->
-			xdotoolCtrl.moveRelative req, resApi
-			expect(resStub.calledOnce).to.eql true
-			expect(resStub.args[0][0]).to.eql 200
+			expect(res.json.calledOnce).to.be.true
+			expect(res.json.calledWith(200)).to.be.true

-	# describe 'mouse left click', ->
-	# 	it 'should call through to the xdotool module', ->
-	# 		xdotoolCtrl.left
+	describe 'mouse left click', ->
+		beforeEach ->
+			xdotool.leftClick.returns('xdotool left cmd')
+			xdotoolCtrl.leftClick req, res

+		it 'should call through to the xdotool module', ->
+			expect(xdotool.leftClick.calledOnce).to.be.true
+
+		it 'should call the commander with the result of xdotool', ->
+			expect(commander.exec.calledOnce).to.be.true
+			expect(commander.exec.calledWith('xdotool left cmd')).to.be.true
+
+		it 'should respond as JSON with 200 status', ->
+			expect(res.json.calledOnce).to.be.true
+			expect(res.json.calledWith(200)).to.be.true
+
+	describe 'mouse right click', ->
+		beforeEach ->
+			xdotool.rightClick.returns('xdotool right cmd')
+			xdotoolCtrl.rightClick req, res
+
+		it 'should call through to the xdotool module', ->
+			expect(xdotool.rightClick.calledOnce).to.be.true
+
+		it 'should call the commander with the result of xdotool', ->
+			expect(commander.exec.calledOnce).to.be.true
+			expect(commander.exec.calledWith('xdotool right cmd')).to.be.true
+
+		it 'should respond as JSON with 200 status', ->
+			expect(res.json.calledOnce).to.be.true
+			expect(res.json.calledWith(200)).to.be.true
+
+	describe 'mouse double click', ->
+		beforeEach ->
+			xdotool.doubleClick.returns('xdotool double cmd')
+			xdotoolCtrl.doubleClick req, res
+
+		it 'should call through to the xdotool module', ->
+			expect(xdotool.doubleClick.calledOnce).to.be.true
+
+		it 'should call the commander with the result of xdotool', ->
+			expect(commander.exec.calledOnce).to.be.true
+			expect(commander.exec.calledWith('xdotool double cmd')).to.be.true
+
+		it 'should respond as JSON with 200 status', ->
+			expect(res.json.calledOnce).to.be.true
+			expect(res.json.calledWith(200)).to.be.true
+


fix duplicated code in server.request

fix duplicated code in server.request

Signed-off-by: Arnaud Richard <791baf8974538c563f287130527dd623cb0e836d@webshell.io>

--- plugins/server.request/server_request.coffee
+++ plugins/server.request/server_request.coffee
@@ -33,9 +33,14 @@
 		async.parallel [
 			(callback) => @db.providers.getExtended provider_name, callback
 			(callback) => @db.apps.getKeyset oauthio.k, provider_name, callback
+			(callback) => @db.apps.checkDomain oauthio.k, ref, callback
 		], (err, results) =>
 			return callback err if err
 			[provider, {parameters}, domaincheck] = results
+
+			req.apiUrl = decodeURIComponent(req.params[1])
+			if ! domaincheck
+				return callback new @check.Error 'Origin "' + ref + '" does not match any registered domain/url on ' + @config.url.host

 			# select oauth version
 			oauthv = oauthio.oauthv && {
@@ -43,19 +48,16 @@
 				"1":"oauth1"
 			}[oauthio.oauthv]
 			if oauthv and not provider[oauthv]
-			return callback new @check.Error "oauthio_oauthv", "Unsupported oauth version: " + oauthv
+				return callback new @check.Error "oauthio_oauthv", "Unsupported oauth version: " + oauthv
+
 			oauthv ?= 'oauth2' if provider.oauth2
 			oauthv ?= 'oauth1' if provider.oauth1

 			parameters.oauthio = oauthio

-			@emit 'request', provider:provider_name, key:oauthio.k
-
 			# let oauth modules do the request
-			oauth[oauthv].request provider, parameters, req, (err, options) ->
-				return callback err if err
-				return callback null, options
-
+			oa = new oauth[oauthv](provider, parameters)
+			oa.request req, callback

 	doRequest = (req, res, next) =>
 		cb = @server.send(res, next)
@@ -74,53 +76,29 @@
 		else
 			origin = urlinfos.protocol + '//' + urlinfos.host

-		async.parallel [
-			(callback) => @db.providers.getExtended req.params[0], callback
-			(callback) => @db.apps.getKeyset oauthio.k, req.params[0], callback
-			(callback) => @db.apps.checkDomain oauthio.k, ref, callback
-		], (err, results) =>
+		@apiRequest req, req.params[0], oauthio, (err, options) ->
 			return cb err if err
-			[provider, {parameters}, domaincheck] = results
+			api_request = null

-			req.apiUrl = decodeURIComponent(req.params[1])
-			if ! domaincheck
-				return cb new @check.Error 'Origin "' + ref + '" does not match any registered domain/url on ' + @config.url.host
+			sendres = ->
+				api_request.pipefilter = (response, dest) ->
+					dest.setHeader 'Access-Control-Allow-Origin', origin
+					dest.setHeader 'Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE'
+				api_request.pipe(res)
+				api_request.once 'end', -> next false

-			# select oauth version
-			oauthv = oauthio.oauthv && {
-				"2":"oauth2"
-				"1":"oauth1"
-			}[oauthio.oauthv]
-			if oauthv and not provider[oauthv]
-				return cb new @check.Error "oauthio_oauthv", "Unsupported oauth version: " + oauthv
-			oauthv ?= 'oauth2' if provider.oauth2
-			oauthv ?= 'oauth1' if provider.oauth1
-			parameters.oauthio = oauthio
-			oa = new oauth[oauthv](provider, parameters)
-			# let oauth modules do the request
-			oa.request req, (err, options) ->
-				return cb err if err
-				api_request = null
-
-				sendres = ->
-					api_request.pipefilter = (response, dest) ->
-						dest.setHeader 'Access-Control-Allow-Origin', origin
-						dest.setHeader 'Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE'
-					api_request.pipe(res)
-					api_request.once 'end', -> next false
-
-				if req.headers['content-type'] and req.headers['content-type'].indexOf('application/x-www-form-urlencoded') != -1
-					bodyParser = restify.bodyParser mapParams:false
-					bodyParser[0] req, res, -> bodyParser[1] req, res, ->
-						options.form = req.body
-						delete options.headers['Content-Length']
-						api_request = request options
-						sendres()
-				else
+			if req.headers['content-type'] and req.headers['content-type'].indexOf('application/x-www-form-urlencoded') != -1
+				bodyParser = restify.bodyParser mapParams:false
+				bodyParser[0] req, res, -> bodyParser[1] req, res, ->
+					options.form = req.body
+					delete options.headers['Content-Length']
 					api_request = request options
-					delete req.headers
-					api_request = req.pipe(api_request)
 					sendres()
+			else
+				api_request = request options
+				delete req.headers
+				api_request = req.pipe(api_request)
+				sendres()

 	@server.opts new RegExp('^' + @config.base + '/request/([a-zA-Z0-9_\\.~-]+)/(.*)$'), (req, res, next) ->


Pass CoffeeLint

Pass CoffeeLint

--- components/RotateElement.coffee
+++ components/RotateElement.coffee
@@ -24,7 +24,10 @@
       return unless @element
       @setRotation @element, degrees
     @inPorts.gpu.on 'data', (gpu) =>
-      @gpuAccelerate = if gpu then 'translateZ(0px) translate3d(0px, 0px, 0px)' else ''
+      @gpuAccelerate =
+        if   gpu
+        then 'translateZ(0px) translate3d(0px, 0px, 0px)'
+        else ''

   setRotation: (element, degrees) ->
     @setVendor element, "transform", "rotate(#{degrees}deg) #{@gpuAccelerate}"


remove unnecessary parameter.

remove unnecessary parameter.

--- coffee/controllers/mainCtrl.coffee
+++ coffee/controllers/mainCtrl.coffee
@@ -22,7 +22,7 @@
         return
       for t in Ticket.get()
         for account in accounts when account.url is t.url
-          Redmine.get(account).getIssuesById t.id, (data, status, headers, config) ->
+          Redmine.get(account).getIssuesById t.id, (data) ->
             newParam =
               subject: data.issue.subject
               text: data.issue.id + ' ' + data.issue.subject


Add basic toggles

Add basic toggles

--- source/javascripts/threejs_scene.js.coffee
+++ source/javascripts/threejs_scene.js.coffee
@@ -6,7 +6,11 @@

 default_vertex_source =
 """
+//uniform vec3 light;
+//varying vec3 vNormal;
+
 void main() {
+//  vNormal = normal;
   gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
 }

@@ -14,8 +18,14 @@

 default_fragment_source =
 """
+//uniform vec3 light;
+//varying vec3 vNormal;
+
 void main() {
-  gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
+//  vec3 light_norm = normalize(light);
+//  float dProd = max(0.0, dot(vNormal, light));
+//  gl_FragColor = vec4(dProd, 0.0, 0.0, 1.0);
+  gl_FragColor = vec4(0.5, 0.5, 0.5, 1.0);
 }

 """
@@ -23,6 +33,9 @@
 class @ThreejsScene
   constructor: (@elem_root, @animate = true, @light = true) ->
     @_stats = new Stats()
+
+    @_uniforms = {}
+
     @_init_root()
     @_create_basic_scene()

@@ -39,6 +52,7 @@

   update_shader: (shader_parameters) ->
     if @_validate_shader_parameters(shader_parameters)
+      shader_parameters.uniforms = @_uniforms
       new_material = new THREE.ShaderMaterial(shader_parameters)
       @mesh.material = new_material
       @shader_material = new_material
@@ -59,13 +73,18 @@
     # Do some animation
     requestAnimationFrame(@render);

+    @_update(time_step)
+
+    @renderer.render(@scene, @camera);
+
+    @_stats.end()
+
+  _update: (time_step) ->
+    @_directional_light.visible = @light
+
     if @animate
       @mesh.rotation.x += 2 * time_step;
       @mesh.rotation.y += 2 * time_step;
-
-    @renderer.render(@scene, @camera);
-
-    @_stats.end()

   _init_root: () ->
     width = $(@elem_root).width()
@@ -152,14 +171,31 @@
     return success

   _create_basic_scene: () ->
+    # Cameras!
+    @camera.position.z = 2
+
+    # Light!
+    @_directional_light = new THREE.DirectionalLight( 0xffffff, 0.75 )
+    @_directional_light.position.set(1,1,2)
+    @scene.add( @_directional_light )
+    @_directional_light.visible = @light
+
+    @_uniforms =
+      light =
+        type: 'v3'
+        value: THREE.Vector3(1,1,2)
+
+    # Models!
     shader_parameters =
+      uniforms: @_uniforms
       vertexShader: default_vertex_source
       fragmentShader: default_fragment_source
     @shader_material = new THREE.ShaderMaterial(shader_parameters)
+    # @shader_material = new THREE.MeshPhongMaterial()

     @geometry = new THREE.CubeGeometry(1,1,1)
+    @geometry.computeFaceNormals()
+    @geometry.computeVertexNormals()

     @mesh = new THREE.Mesh( @geometry, @shader_material )
     @scene.add( @mesh )
-
-    @camera.position.z = 1.5


remove unnecessary return

remove unnecessary return

--- app/assets/javascripts/datepicker.js.coffee
+++ app/assets/javascripts/datepicker.js.coffee
@@ -8,4 +8,3 @@
     showOtherMonths:    true
     selectOtherMonths:  true
     showButtonPanel:    true
-  return


bug solved date format

bug solved date format

--- app/assets/javascripts/datepicker.js.coffee
+++ app/assets/javascripts/datepicker.js.coffee
@@ -2,5 +2,5 @@
   $("input.datepicker").each (i) ->
     $(this).datepicker
       altFormat: "yy-mm-dd"
-      dateFormat: "mm/dd/yy"
+      dateFormat: "dd/mm/yy"
       altField: $(this).next()


include first day in timeline

include first day in timeline

--- app/assets/javascripts/history-chart.coffee
+++ app/assets/javascripts/history-chart.coffee
@@ -110,7 +110,7 @@
   if timelineBounds?
     chartOpts = merge chartOpts,
       xaxis:
-        min: timelineBounds.start
+        min: moment(timelineBounds.start).subtract(1, 'days').valueOf()
         max: timelineBounds.finish

   plot = $.plot $("#" + chartId), series, chartOpts


Fix copying state from addresses

Fix copying state from addresses

--- app/assets/javascripts/residences.js.coffee
+++ app/assets/javascripts/residences.js.coffee
@@ -10,7 +10,7 @@
       break
   unless match
     return
-  address_fields.find('input[id$=state]').val(match.state)
+  address_fields.find('select[id$=state]').val(match.state)
   address_fields.find('input[id$=street]').val(match.street)
   address_fields.find('input[id$=city]').val(match.city)
   address_fields.find('input[id$=apt]').val(match.apt)


Fix outdated API usage.

Fix outdated API usage.

--- app/assets/javascripts/services/user.coffee
+++ app/assets/javascripts/services/user.coffee
@@ -4,7 +4,8 @@
     User = $resource '/api/user.json'
     me = User.me = new User(config.user)

-    if zone = $window.Intl?.DateTimeFormat().resolved.timeZone
+    format = $window.Intl?.DateTimeFormat()
+    if format? && zone = (format.resolvedOptions?().timeZone ? format.resolved?.timeZone)
       me.$update(time_zone_auto: zone) if me.time_zone_auto != zone
     else if ! me.time_zone_auto?
       me.$update(time_zone_auto: (new Date).getTimezoneOffset())


Simplified FactlinkJailRoot.Highlight

Simplified FactlinkJailRoot.Highlight

--- app/js/jail_iframe/classes/highlight.coffee
+++ app/js/jail_iframe/classes/highlight.coffee
@@ -1,17 +1,9 @@
-highlight_time_on_in_view = 1500
-
-class Highlighter
-  constructor: (@$elements, @className) ->
-    throw 'Higlighter requires class' unless @className
-
-  highlight:   -> @$elements.addClass(@className)
-  dehighlight: -> @$elements.removeClass(@className)
-
-
 class FactlinkJailRoot.Highlight
   constructor: (@id, @elements) ->
     @show_button = new FactlinkJailRoot.ShowButton @elements, @id
-    @core_highlighter = new Highlighter $(@elements), 'fl-core-highlight'
+
+  highlight:   -> @$elements.addClass('fl-core-highlight')
+  dehighlight: -> @$elements.removeClass('fl-core-highlight')

   destroy: ->
     for el in @elements
@@ -22,9 +14,9 @@
 previousCoreHighlightId = null
 FactlinkJailRoot.showCoreHighlight = (factId) ->
   for highlight in FactlinkJailRoot.highlightsByFactIds[previousCoreHighlightId] || []
-    highlight.core_highlighter.dehighlight()
+    highlight.dehighlight()

   for highlight in FactlinkJailRoot.highlightsByFactIds[factId] || []
-    highlight.core_highlighter.highlight()
+    highlight.highlight()

   previousCoreHighlightId = factId


Update firefox version.

Update firefox version.

--- resources/assets/js/outer/versions.coffee
+++ resources/assets/js/outer/versions.coffee
@@ -2,7 +2,7 @@
   v: (v) ->
     "#{v} #{CI.Versions[v]}"

-  Firefox: "33.0"
+  Firefox: "34.0"
   Chrome: "38.0.2125.122"
   chromedriver: "2.12"


I'm laughing at your name.

I'm laughing at your name.

--- src/scripts/archer.coffee
+++ src/scripts/archer.coffee
@@ -19,6 +19,9 @@
 _          = require("underscore")

 module.exports = (robot) ->
+
+  robot.respond /benoit/i, (msg) ->
+    msg.send "balls"

   robot.respond /archer quote$/i, (msg) ->
     msg


Fix block comment

Fix block comment

--- public/assets/coffee/order-id-payment.coffee
+++ public/assets/coffee/order-id-payment.coffee
@@ -39,7 +39,7 @@
       else
         console.log '** FAIL'

-  ### FIXME: 테스트를 위해서 결제를 생략하고, 상태변경을 요청
+  ###
   STATUS =
     choose_address: 49
     payment_done: 50
@@ -91,3 +91,4 @@
       error: (jqXHR, textStatus, errorThrown) ->
       complete: (jqXHR, textStatus) ->
         $this.removeClass('disabled')
+  ###


Sorting classifies by category names.

Sorting classifies by category names.

--- web/coffee/management-problems.coffee
+++ web/coffee/management-problems.coffee
@@ -74,6 +74,7 @@
       size: problems.length
       classifier: (problem) ->
         problem.category == category
+    categoryData = _.sortBy categoryData, "name"

     organizations = _.groupBy @props.problems, "organization"
     organizationData = _.map organizations, (problems, organization) ->
@@ -81,6 +82,7 @@
       size: problems.length
       classifier: (problem) ->
         problem.organization == organization
+    organizationData = _.sortBy organizationData, "name"

     problemStates = _.countBy @props.problems, "disabled"
     problemStateData = []


SendGraphChanges: monitor and send group changes

--- components/SendGraphChanges.coffee
+++ components/SendGraphChanges.coffee
@@ -53,6 +53,10 @@
     @graph.on 'addOutport', @addOutport
     @graph.on 'removeOutport', @removeOutport
     @graph.on 'renameOutport', @renameOutport
+    @graph.on 'addGroup', @addGroup
+    @graph.on 'removeGroup', @removeGroup
+    @graph.on 'renameGroup', @renameGroup
+    @graph.on 'changeGroup', @changeGroup
     @subscribed = true

   unsubscribe: ->
@@ -71,6 +75,10 @@
     @graph.removeListener 'addOutport', @addOutport
     @graph.removeListener 'removeOutport', @removeOutport
     @graph.removeListener 'renameOutport', @renameOutport
+    @graph.removeListener 'addGroup', @addGroup
+    @graph.removeListener 'removeGroup', @removeGroup
+    @graph.removeListener 'renameGroup', @renameGroup
+    @graph.removeListener 'changeGroup', @changeGroup
     @subscribed = false

     @outPorts.sent.disconnect()
@@ -176,6 +184,35 @@
       from: oldPub
       to: newPub
       graph: @graph.properties.id
+
+  addGroup: (group) =>
+    @registerChange 'addgroup',
+      name: group.name
+      nodes: group.nodes
+      metadata: group.metadata
+      graph: @graph.properties.id
+
+  removeGroup: (group) =>
+    @registerChange 'removegroup',
+      name: group.name
+      graph: @graph.properties.id
+
+  renameGroup: (oldName, newName) =>
+    @registerChange 'renamegroup',
+      from: oldName
+      to: newName
+      graph: @graph.properties.id
+
+  changeGroup: (group, before) =>
+    key = 'changegroup-' + group.name
+    metadata =
+      name: group.name
+      metadata: group.metadata
+      graph: @graph.properties.id
+    if @changesStates[key] is undefined
+      @changesStates[key] = @registerChange 'changegroup', metadata
+    else
+      @replaceChange @changesStates[key], 'changegroup', metadata

   send: =>
     return unless @runtime


improve returning to scroll position

improve returning to scroll position

--- components/feed/client/feed.coffee
+++ components/feed/client/feed.coffee
@@ -66,7 +66,6 @@

     $el = @$("a[href='#{href}']")
     return unless $el.length
-    @$htmlBody.scrollTop $el.offset().top - 200
     @$el.prepend """
       <div class='feed-previous-button'>
         <button class='avant-garde-button-text'>
@@ -74,7 +73,8 @@
         </button>
       </div>
     """
-    @$htmlBody.imagesLoaded => @$htmlBody.scrollTop $el.offset().top - 200
+    @$htmlBody.imagesLoaded =>
+      @$htmlBody.scrollTop($el.offset().top - 200)

   storeOptions: (options) ->
     @feedItems             = options.feedItems


destroy the feed if it isnt visible

destroy the feed if it isnt visible

--- components/feed/client/feed.coffee
+++ components/feed/client/feed.coffee
@@ -192,18 +192,15 @@
     return if @feedItems.doneFetching or @waiting
     return unless @lastItem?.length

-
     top = if @lastItem.offset() then @lastItem.offset().top else 0
     if @scrollTop + 1500 > top
       if @$el.is(':visible')
         @fetchMoreItems()
       else
-        # Destroy the feed if it isn't visible
-        # - helps with filtering
+        # Destroy the feed if it isn't visible (helps with fair filtering)
         @destroy()

     @trackScroll()
-

   scrollPositionsTracked: {}
   scrollInterval: 3000
@@ -234,7 +231,7 @@
     @currentUser?.initializeDefaultArtworkCollection()
     @artworkCollection = @currentUser?.defaultArtworkCollection()

-  destroy: ->
+  destroy: =>
     @$el.html ''
     @$window.off '.feed'


Forgot gruntfile change for gh-pages dist

--- reveal-slides/Gruntfile.coffee
+++ reveal-slides/Gruntfile.coffee
@@ -29,7 +29,7 @@
             jshint:
                 files: ['js/*.js']
                 tasks: ['jshint']
-
+
         connect:

             livereload:
@@ -68,6 +68,7 @@
                         'slides/**'
                         'bower_components/**'
                         'js/**'
+                        'img/**'
                     ]
                     dest: 'dist/'
                 },{
@@ -77,7 +78,7 @@
                     filter: 'isFile'
                 }]

-
+
         buildcontrol:

             options:
@@ -89,7 +90,7 @@
                 options:
                     remote: 'git@github.com:willbuck/AngularJSForGrails.git'
                     branch: 'gh-pages'
-
+

     # Load all grunt tasks.
@@ -137,13 +138,13 @@
             'copy'
         ]

-
+
     grunt.registerTask 'deploy',
         'Deploy to Github Pages', [
             'dist'
             'buildcontrol'
         ]
-
+

     # Define default task.
     grunt.registerTask 'default', [


do not run in verbose mode

do not run in verbose mode

--- src/steroids/Grunt.coffee
+++ src/steroids/Grunt.coffee
@@ -12,7 +12,7 @@

     require(paths.includedGrunt).tasks ["default"],
       gruntfile: path.join @workingPath, 'grunt.js'
-      verbose: true
+      verbose: false

   configFilePath: (workingPath = @workingPath) ->
     path.join workingPath, "grunt.js"


Handle scrolling on Reddit redesign

Handle scrolling on Reddit redesign

--- content_scripts/scroller.coffee
+++ content_scripts/scroller.coffee
@@ -319,6 +319,15 @@
         activatedElement = element ? getScrollingElement()
         func arguments...

+if DomUtils.isTopFrame() and window.location.host in ["reddit.com", "new.reddit.com"]
+  for method in ["scrollTo", "scrollBy"]
+    do ->
+      func = Scroller[method]
+      Scroller[method] = ->
+        element = document.getElementById "overlayScrollContainer"
+        activatedElement = element ? getScrollingElement()
+        func arguments...
+
 root = exports ? (window.root ?= {})
 root.Scroller = Scroller
 extend window, root unless exports?


Smooth scroll; admit count prefixes.

Smooth scroll; admit count prefixes.

--- content_scripts/scroller.coffee
+++ content_scripts/scroller.coffee
@@ -88,7 +88,6 @@
 doScrollBy = do ->
   # This is logical time. Time is advanced each time an animator is activated, and on each keyup event.
   time = 0
-  mostRecentActivationTime = -1
   lastEvent = null
   keyHandler = null

@@ -108,14 +107,10 @@
       checkVisibility element
       return

-    if mostRecentActivationTime == time or lastEvent?.repeat
-      # Either the most-recently activated animator has not yet received its keyup event (so it's still
-      # scrolling), or this is a keyboard repeat (for which we don't initiate a new animator).
-      # NOTE(smblott) We need both of these checks because sometimes (perhaps one time in twenty) the last
-      # keyboard repeat arrives *after* the corresponding keyup.
-      return
-
-    mostRecentActivationTime = activationTime = ++time
+    # We don't activate new animators on keyboard repeats.
+    return if lastEvent?.repeat
+
+    activationTime = ++time

     isKeyStillDown = ->
       time == activationTime


KDTimeAgoView: Updating view when new data set.

--- components/time/timeagoview.coffee
+++ components/time/timeagoview.coffee
@@ -6,11 +6,15 @@

   constructor: (options = {}, data) ->

-    options.tagName = 'time'
+    options.tagName = "time"

     super options, data

     KDTimeAgoView.on "OneMinutePassed", => @updatePartial $.timeago @getData()

+  setData: ->
+    super
+    @updatePartial $.timeago @getData()  if @parent
+
   viewAppended: ->
     @setPartial $.timeago @getData()


Fixes wrong scope for permission event

Fixes wrong scope for permission event

--- vendor/assets/javascript/knotify.coffee
+++ vendor/assets/javascript/knotify.coffee
@@ -25,7 +25,7 @@

 class KnotifyPermission
   constructor: (target, event = 'click') ->
-    document.querySelector(target).addEventListener event, ->
+    document.querySelector(target).addEventListener event, =>
       @_getPermission() unless @_checkPermission() == 0

   _getPermission: ->


react course: properly display times for each assign/collect/etc/

--- salvus/page/editor_course.cjsx
+++ salvus/page/editor_course.cjsx
@@ -229,8 +229,9 @@
             target_users.map (_, account_id) =>
                 if not users?.get(account_id)?
                     invite(account_id)
-            # Make sure nobody else is on the student's project (anti-cheating measure)
-            flux.getStore('projects').get_users(student_project_id).map (_,account_id) =>
+            # Make sure nobody else is on the student's project (anti-cheating measure) -- but only if project
+            # already created.
+            flux.getStore('projects').get_users(student_project_id)?.map (_,account_id) =>
                 if not target_users.get(account_id)? and account_id != student_account_id
                     flux.getActions('projects').remove_collaborator(student_project_id, account_id)

@@ -818,7 +819,7 @@
         user_map   : rtypes.object.isRequired
         info       : rtypes.func.isRequired

-    render_event: (name, obj) ->
+    render_last: (name, obj) ->
         v = [{name+': '}]
         if obj?
             if obj.time?
@@ -832,13 +833,13 @@
     render_student_info: (info) ->
         <Row >
             <Col md=4 key='last_assignment'>
-                {@render_event('Assigned', info.last_assignment)}
+                {@render_last('Assigned', info.last_assignment)}
             </Col>
             <Col md=4 key='collect'>
-                {@render_event('Collected', info.collect)}
+                {@render_last('Collected', info.last_collect)}
             </Col>
             <Col md=4 key='return_graded'>
-                {@render_event('Returned', info.return_graded)}
+                {@render_last('Returned', info.last_return_graded)}
             </Col>
         </Row>


fix(poll): poll common card last stance reactive

--- vue/src/shared/models/poll_model.coffee
+++ vue/src/shared/models/poll_model.coffee
@@ -10,7 +10,7 @@
 export default class PollModel extends BaseModel
   @singular: 'poll'
   @plural: 'polls'
-  @indices: ['discussionId', 'authorId']
+  @indices: ['discussionId', 'authorId', 'latest']
   @draftParent: 'draftParent'
   @draftPayloadAttributes: ['title', 'details']


There will always be a currentNode

There will always be a currentNode

--- src/trix/text_view.coffee
+++ src/trix/text_view.coffee
@@ -44,9 +44,8 @@
     @positions = []
     walker = document.createTreeWalker(@element)
     while walker.nextNode()
-      if node = walker.currentNode
-        if node.trixPosition?
-          @recordNode(node)
+      if walker.currentNode.trixPosition?
+        @recordNode(walker.currentNode)

   recordNode: (node) ->
     @nodes.push(node)


Visual/edit modes: fix restore of insert-mode sub mode.

--- content_scripts/mode_insert.coffee
+++ content_scripts/mode_insert.coffee
@@ -48,7 +48,7 @@
         # changes, we might get "focus" before "blur".  We track the active element in @insertModeLock, and
         # exit only when that element blurs.
         # We don't exit if we're running under edit mode.  Edit mode itself will handles that case.
-        @exit event, target if @insertModeLock and target == @insertModeLock and not @options.editModeParent
+        @exit event, target if @insertModeLock and target == @insertModeLock and not @options.parentMode
       "focus": (event) => @alwaysContinueBubbling =>
         if @insertModeLock != event.target and DomUtils.isFocusable event.target
           @activateOnElement event.target


changed from array.prototype to query object

--- coffee/edn.coffee
+++ coffee/edn.coffee
@@ -3,6 +3,7 @@

 @edn = (json) ->
   return json if _.isString json
+  return json.edn() if json.edn?
   edn.encode json

 @json = (edn_str) ->
@@ -11,6 +12,13 @@
   catch e
     edn_str

-@find = (args...) -> [':find'].concat args
+@find = (args...) -> new Query args
+
+class Query

-Array.prototype.where = (args...) -> @.concat [':where', args]
+  constructor: (args) ->
+    @data = [':find'].concat args
+
+  where: (args...) -> @data = @data.concat [':where', args]
+
+  edn: -> edn @data


Generating source link in docs fixed

Generating source link in docs fixed

--- scripts/api-docs/jobs/source-to-links.coffee
+++ scripts/api-docs/jobs/source-to-links.coffee
@@ -13,7 +13,7 @@
         if type instanceof Heading and type.getLevel() <= 4
             heading = type
         else if type instanceof ProgramCode
-            url = pathUtils.join URL_PREFIX, fileCommit, '/', path
+            url = URL_PREFIX + fileCommit + '/' + pathUtils.normalize(path)
             if heading?.syntax
                 url += headingToUrl heading.syntax
             text = "\n> [`Source`](#{url})\n"


remove unused commented-out, old-style #monitorServices method from auth worker

--- workers/auth/lib/auth/authworker.coffee
+++ workers/auth/lib/auth/authworker.coffee
@@ -16,21 +16,8 @@
       byRoutingKey  : {}
     }
     @counts   = {}
-    # @monitorServices()

   bound: require 'koding-bound'
-
-  # monitorServices: do ->
-  #   pingAll =(services)->
-  #     # for own servicesOfType of services
-  #     #   servicesOfType.forEach (service)->
-  #     # TODO: implement pingA
-  #   monitorServicesHelper =->
-  #     console.log {@services, @clients}
-  #     pingAll @services
-  #   monitorServices =->
-  #     handler = monitorServicesHelper.bind this
-  #     setInterval handler, 10000

   authenticate:(messageData, routingKey, callback)->
     {clientId, channel, event} = messageData
@@ -170,9 +157,11 @@
       member    : @resourceName
     }
     @presence.on 'join', (serviceKey)=>
+      console.log 'j', {serviceKey}
       try @addService parseServiceKey serviceKey
       catch e then console.error e
     @presence.on 'leave', (serviceKey)=>
+      console.log 'l', {serviceKey}
       try @removeService parseServiceKey serviceKey
       catch e then console.error e
     @presence.listen()


replies count

replies count

--- server/app/bongo/models/messages/post.coffee
+++ server/app/bongo/models/messages/post.coffee
@@ -321,40 +321,47 @@
             @addComment comment,
               flags:
                 isLowQuality    : exempt
-              respondWithCount  : yes
-            , (err, docs, count)=>
+            , (err, docs)=>
               if err
                 callback err
               else
                 if exempt
                   callback null, comment
                 else
-                  @update $set: repliesCount: count, (err)=>
+                  Relationship.count {
+                    sourceId                    : @getId()
+                    as                          : 'reply'
+                    'data.flags.isLowQuality'   : $ne: yes
+                  }, (err, count)=>
                     if err
                       callback err
                     else
-                      callback null, comment
-                      @fetchActivityId (err, id)->
-                        CActivity.update {_id: id}, {
-                          $set: 'sorts.repliesCount': count
-                        }, log
-                      @fetchOrigin (err, origin)=>
+                      @update $set: repliesCount: count, (err)=>
                         if err
-                          console.log "Couldn't fetch the origin"
+                          callback err
                         else
-                          unless exempt
-                            @emit 'ReplyIsAdded', {
-                              origin
-                              subject       : ObjectRef(@).data
-                              actorType     : 'replier'
-                              actionType    : 'reply'
-                              replier 		  : ObjectRef(delegate).data
-                              reply   		  : ObjectRef(comment).data
-                              repliesCount	: count
-                              relationship  : docs[0]
-                            }
-                          @follow client, emitActivity: no, (err)->
-                          @addParticipant delegate, 'commenter', (err)-> #TODO: what should we do with this error?
+                          callback null, comment
+                          @fetchActivityId (err, id)->
+                            CActivity.update {_id: id}, {
+                              $set: 'sorts.repliesCount': count
+                            }, log
+                          @fetchOrigin (err, origin)=>
+                            if err
+                              console.log "Couldn't fetch the origin"
+                            else
+                              unless exempt
+                                @emit 'ReplyIsAdded', {
+                                  origin
+                                  subject       : ObjectRef(@).data
+                                  actorType     : 'replier'
+                                  actionType    : 'reply'
+                                  replier 		  : ObjectRef(delegate).data
+                                  reply   		  : ObjectRef(comment).data
+                                  repliesCount	: count
+                                  relationship  : docs[0]
+                                }
+                              @follow client, emitActivity: no, (err)->
+                              @addParticipant delegate, 'commenter', (err)-> #TODO: what should we do with this error?

   # TODO: the following is not well-factored.  It is not abstract enough to belong to "Post".
   # for the sake of expedience, I'll leave it as-is for the time being.


test to check that stops binding are not lost when new stops are added

--- tests/vehicle.coffee
+++ tests/vehicle.coffee
@@ -101,6 +101,38 @@
   SC.run.sync()
   equal journey1.get('foo'), false, "the binding still works"

+test 'bindings to a stop are not lost when a new stop is created', ->
+  vehicle =  Sensor.Vehicle.create()
+  messages = messageFactory [
+   {usn: Sensor.IGNITION_ON,  time: '01:10'},
+   {usn: Sensor.MOVING,       time: '01:11'},
+   {usn: Sensor.IGNITION_OFF, time: '01:12'}
+  ]
+  vehicle.updateWithMessages messages
+  SC.run.sync()
+  Sensor.stop1 = vehicle.getPath('stops.firstObject')
+  boundObject = SC.Object.create fooBinding: 'Sensor.stop1.foo'
+  SC.run.sync()
+  equal Sensor.stop1.get('foo'), undefined, "stop1#foo should not have changed before the sync"
+  boundObject.set('foo', true)
+  SC.run.sync()
+  equal Sensor.stop1.get('foo'), true, "the binding should have changed stop1#foo"
+  # Create a new stop
+  messages = messageFactory [
+   {usn: Sensor.IGNITION_ON,  time: '02:10'},
+   {usn: Sensor.MOVING,       time: '02:11'},
+   {usn: Sensor.IGNITION_OFF, time: '02:12'}
+  ]
+  vehicle.updateWithMessages messages
+  SC.run.sync()
+  equal vehicle.getPath('stops.length'), 2
+  # Check that the binding is still in place
+  stop1 = vehicle.getPath('stops.firstObject')
+  equal stop1.get('foo'), true, "the value hasn't changed"
+  boundObject.set('foo', false)
+  SC.run.sync()
+  equal stop1.get('foo'), false, "the binding still works"
+
 test 'moved', ->
   vehicle =  Sensor.Vehicle.create()
   equal vehicle.get('moved'), false


Overriding versions when running local

Overriding versions when running local

--- tests/www/run.coffee
+++ tests/www/run.coffee
@@ -19,6 +19,7 @@
           continue if not browser_conf.local

           browser_conf.platform = null
+          browser_conf.version = null

         # otherwise, skipe local_only configs
         else if browser_conf.local_only is true


Disabling stackTrace in chai.

Disabling stackTrace in chai.

--- tests/www/run.coffee
+++ tests/www/run.coffee
@@ -4,8 +4,6 @@
 browsers = require './utils/browsers'

 files = fsu.find (path.join __dirname, 'tests'), /\.coffee$/m
-
-(require 'chai').Assertion.includeStack = true

 exports.test = ( name, conf, base_url, mark_as_passed )->


update generateCreateTeamRequestParams helper

--- servers/testhelper/handler/teamhelper.coffee
+++ servers/testhelper/handler/teamhelper.coffee
@@ -1,6 +1,8 @@
-querystring = require 'querystring'
+JTeamInvitation = require '../../../workers/social/lib/social/models/teamtoken'

-{ generateUrl
+{ expect
+  generateUrl
+  querystring
   deepObjectExtend
   generateRandomEmail
   generateRandomString
@@ -124,19 +126,20 @@
   companyName = "testcompany#{generateRandomString(10)}"

   defaultBodyObject =
-    slug           :  companyName
-    email          :  generateRandomEmail()
-    agree          :  'on'
-    allow          :  'true'
-    domains        :  'koding.com, gmail.com'
-    invitees       :  invitees
-    redirect       :  ''
-    username       :  username
-    password       :  'testpass'
-    newsletter     :  'true'
-    companyName    :  companyName
-    alreadyMember  :  'false'
-    passwordConfirm:  'testpass'
+    slug           : companyName
+    email          : generateRandomEmail()
+    agree          : 'on'
+    allow          : 'true'
+    domains        : 'koding.com, gmail.com'
+    invitees       : invitees
+    redirect       : ''
+    username       : username
+    password       : 'testpass'
+    newsletter     : 'true'
+    companyName    : companyName
+    alreadyMember  : 'false'
+    teamAccessCode : ''
+    passwordConfirm: 'testpass'

   deepObjectExtend defaultBodyObject, opts

@@ -144,7 +147,7 @@

 # overwrites given options in the default params
-generateCreateTeamRequestParams = (opts = {}) ->
+generateCreateTeamRequestParams = (opts = {}, callback) ->

   url  = generateUrl
     route : '-/teams/create'
@@ -154,10 +157,21 @@
   params               = { url, body }
   defaultRequestParams = generateDefaultRequestParams params
   requestParams        = deepObjectExtend defaultRequestParams, opts
-  # after deep extending object, encodes body param to a query string
-  requestParams.body   = querystring.stringify requestParams.body

-  return requestParams
+  # return without creating a team invitation
+  if opts.createTeamInvitation is no
+    # after deep extending object, encodes body param to a query string
+    requestParams.body = querystring.stringify requestParams.body
+    return callback requestParams
+
+  slug  = opts.body ? body.slug
+  email = opts.email ? body.email
+
+  JTeamInvitation.create { groupName : slug, email }, (err, teamInvitation) ->
+    expect(err).to.not.exist
+    requestParams.body.teamAccessCode = teamInvitation.code
+    requestParams.body                = querystring.stringify requestParams.body
+    return callback requestParams

 module.exports = {


女神回复模式

女神回复模式

--- src/plugins/faction/fallback.coffee
+++ src/plugins/faction/fallback.coffee
@@ -10,7 +10,14 @@

         r = FactionUtil.parseCallingBody item

-        FactionUtil.send Bot.generateTemplate('@{player} {smily:surprise}').fillPlayer(r.player).fillSmily().toString()
+        FactionUtil.send Bot.getTemplate([
+            ['@{player}'],
+            [
+                '呵呵'
+                '嗯?'
+                '{smily:surprise}'
+            ]
+        ]).fillPlayer(r.player).fillSmily().toString()

         callback()


Fix crash that occurred when opening a file using the LocalFile provider tab in the open dialog and then saving it

--- src/code/views/file-dialog-tab-view.coffee
+++ src/code/views/file-dialog-tab-view.coffee
@@ -91,8 +91,21 @@
   listLoaded: (list) ->
     @setState list: list

+  getSaveMetadata: ->
+    # The save metadata for a file that may have been opened from another
+    # provider must be cloned, but without cloning the provider field.
+    # Furthermore, if the provider has changed, the provider and providerData
+    # fields should be cleared.
+    saveMetadata = _.clone @props.client.state.metadata
+    if @props.provider is saveMetadata.provider
+      saveMetadata.providerData = _.cloneDeep saveMetadata.providerData
+    else
+      saveMetadata.provider = null
+      saveMetadata.providerData = null
+    saveMetadata
+
   getStateForFolder: (folder) ->
-    metadata = if @isOpen() then @state?.metadata or null else _.cloneDeep @props.client.state.metadata
+    metadata = if @isOpen() then @state?.metadata or null else @getSaveMetadata()
     metadata?.parent = folder

     if @props.client.state.metadata and (@props.provider isnt @props.client.state.metadata.provider)


Setting crawler flag into the window instead of the `@the` instance.

--- www/src/theoricus/core/processes.coffee
+++ www/src/theoricus/core/processes.coffee
@@ -184,7 +184,7 @@
         @_run_pending_processes()
     else
       @locked = false
-      @the.crawler.is_rendered = true
+      window.crawler.is_rendered = true

       if @disable_transitions?
         @the.config.disable_transitions = @disable_transitions


Changed the way the envelope.user is build so it won't clash with hubot-irc

--- src/scripts/jenkins-notifier.coffee
+++ src/scripts/jenkins-notifier.coffee
@@ -58,10 +58,11 @@
     res.end('')

     envelope = {notstrat:"Fs"}
-    envelope.user = {}
     envelope.room = query.room if query.room
-    envelope.user.type = query.type if query.type
     envelope.notstrat = query.notstrat if query.notstrat
+    if query.type
+      envelope.user = {type: query.type}
+
     try
       data = req.body


Let's get those build notifications out of #general and into #dev

--- src/scripts/jenkins-notifier.coffee
+++ src/scripts/jenkins-notifier.coffee
@@ -98,7 +98,7 @@
           if data.build.parameters.SERVER_HOSTNAME == 'deathstar.1stdibs.com'
             http.get 'http://xserve:5051/deathstar', (res) ->
               console.log res.statusCode
-            robot.messageRoom "#general", "The Death Star is now fully armed and operational"
+            robot.messageRoom "#dev", "The Death Star is now fully armed and operational"

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


Fix for 32bit windows

Fix for 32bit windows

--- src/services/gateblu-service.coffee
+++ src/services/gateblu-service.coffee
@@ -4,6 +4,8 @@
 debug = require('debug')('gateblu-ui:GatebluService')
 fs = require 'fs-extra'
 {exec} = require 'child_process'
+
+PROGRAMFILES = process.env['PROGRAMFILES(X86)'] || process.env['PROGRAMFILES']

 class GatebluService
   EVENTS_TO_FORWARD = [
@@ -106,7 +108,7 @@
       return "/Library/Octoblu/GatebluService/package.json"

     if process.platform == 'win32'
-      return "#{process.env['PROGRAMFILES(X86)']}\\Octoblu\\GatebluService\\package.json"
+      return "#{PROGRAMFILES}\\Octoblu\\GatebluService\\package.json"

     return './meshblu.json'

@@ -116,7 +118,7 @@
         return callback error

     if process.platform == 'win32'
-      exec "start \"GatebluServiceTray\" \"#{process.env['PROGRAMFILES(X86)']}\\Octoblu\\GatebluService\\GatebluServiceTray.exe\"", (error, stdout, stdin) =>
+      exec "start \"GatebluServiceTray\" \"#{PROGRAMFILES}\\Octoblu\\GatebluService\\GatebluServiceTray.exe\"", (error, stdout, stdin) =>
         return callback error

     callback new Error "Unsupported Operating System"


Refactor, done.

Refactor, done.

--- client/app/MainApp/paymentcontroller.coffee
+++ client/app/MainApp/paymentcontroller.coffee
@@ -257,7 +257,9 @@
     else
       KD.remote.api.JRecurlyPlan.getUserAccount cb

-  createPaymentConfirmationModal: (type, group, plan, content, needBilling, willCharge, cb)->
+  createPaymentConfirmationModal: (type, group, plan, needBilling, balance, amount, cb)->
+    content = @paymentWarning balance, amount
+
     modal           = new KDModalView
       title         : "Confirm VM Creation"
       content       : "
#{content}
"
@@ -282,13 +284,34 @@
           cssClass  : "modal-clean-green hidden"
           callback  : =>
             modal.destroy()
-            @makePayment type, plan, willCharge, ->
+            @makePayment type, plan, amount, ->
               cb()

     if needBilling
       modal.buttons.Billing.show()
     else
       modal.buttons.Yes.show()
+
+  paymentWarning: do->
+
+    formatMoney = (amount)-> (amount / 100).toFixed 2
+
+    (balance, amount)->
+      content = ""
+
+      chargeAmount = Math.max amount - balance, 0
+
+      if amount is 0
+        content += "
You are already subscribed for an extra VM.
"
+      else if balance > 0
+        content += "
You have $#{formatMoney balance} credited to your account.
"
+
+      if chargeAmount > 0
+        content += "
You will be charged for $#{formatMoney chargeAmount}
"
+      else
+        content += "
You won't be charged for this
"
+
+      content

   confirmPayment:(type, plan, callback=->)->
     group = KD.getSingleton("groupsController").getCurrentGroup()
@@ -298,59 +321,21 @@
       planCode : plan.code
     , (err, status)=>
       if not err and status
-        content = """

-                       You already subscribed for an additional
-                       #{type} VM.
-

-

-                       You won't be charged. Do you want to
-                       continue?.
-

-                  """
-        buildModal content, no, no, callback
+        @createPaymentConfirmationModal type, group, plan, no, 0, 0, callback
       else
         @getBillingInfo type, group, (err, account)=>
-          billing = err or not account or not account.cardNumber
+          needBilling = err or not account or not account.cardNumber

           @getBalance type, group, (err, balance)=>
-            if not err and balance > 0
-              charge = (plan.feeMonthly - balance) / 100
-              balance = balance / 100
-
-              content = """

-                             You have $#{balance.toFixed(2)} credited to your
-                             #{type} account.
-

-                        """
-
-              if charge > 0
-                content += """

-                                You will be charged for $#{charge.toFixed(2)}.
-                                Do you want to continue?
-

-                           """
-                @createPaymentConfirmationModal type, group, plan, content, billing, yes, callback
-              else
-                content += """

-                                You won't be charged. Do you
-                                want to continue?
-

-                           """
-                @createPaymentConfirmationModal type, group, plan, content, no, no, callback
-            else
-              charge = plan.feeMonthly / 100
-              content = """

-                             You will be charged for $#{charge.toFixed(2)}. Do
-                             you want to continue?
-

-                        """
-              @createPaymentConfirmationModal type, group, plan, content, billing, yes, callback
-
-  makePayment: (type, plan, willCharge, callback)->
+            if err
+              balance = 0
+            @createPaymentConfirmationModal type, group, plan, needBilling, balance, plan.feeMonthly, callback
+
+  makePayment: (type, plan, amount, callback)->
     vmController = KD.getSingleton('vmController')
     group        = KD.getSingleton("groupsController").getCurrentGroup()

-    unless willCharge
+    if amount is 0
       vmController.createGroupVM type, plan.code
     else
       if type is 'group'


landing: build: added a desktop notifier

--- client/landing/gulptasks/task.export.coffee
+++ client/landing/gulptasks/task.export.coffee
@@ -1,6 +1,7 @@
-gulp  = require 'gulp'
-shell = require 'gulp-shell'
-argv  = require('minimist') process.argv
+gulp   = require 'gulp'
+shell  = require 'gulp-shell'
+argv   = require('minimist') process.argv
+notify = require 'gulp-notify'

 { log }        = require './helper.logger'
 { BUILD_PATH } = require './helper.constants'
@@ -21,3 +22,4 @@

   gulp.src ''
     .pipe shell cmds
+    .pipe notify "files exported!"


Fixed: completionDate was not set during archive action

--- client/src/actions/TaskActionCreator.coffee
+++ client/src/actions/TaskActionCreator.coffee
@@ -175,8 +175,10 @@
             AppDispatcher.handleViewAction
                 type: ActionTypes.ARCHIVE_TASK
                 value: task.cid
-
-            XHRUtils.update task.id, isArchived: true, (err) -> callback err
+            payload =
+                isArchived: true
+                completionDate: Date.now()
+            XHRUtils.update task.id, payload, (err) -> callback err
         , (err) ->
             # handle error case

@@ -184,8 +186,11 @@
         AppDispatcher.handleViewAction
             type: ActionTypes.RESTORE_TASK
             value: task.cid
-
-        XHRUtils.update task.id, isArchived: false, done: false, (err, task) ->
+        payload =
+            isArchived: false
+            done: false
+            completionDate: null
+        XHRUtils.update task.id, payload, (err, task) ->
             # handle error case

     reindexTasks: ->


stack-editor: use flux action to delete a stack template

--- client/stack-editor/lib/editor/index.coffee
+++ client/stack-editor/lib/editor/index.coffee
@@ -729,22 +729,15 @@
     description = 'Do you want to remove this stack template ?'
     callback    = ({ status, modal }) ->
       return  unless status
-      template.delete (err) ->
-
-        if err
+
+      EnvironmentFlux.actions.removeStackTemplate template
+        .then ->
+          router.handleRoute '/IDE'
+          modal.destroy()
+        .catch (err) ->
           new kd.NotificationView { title: 'Something went wrong!' }
           modal.destroy()
-          return
-
-        if template.accessLevel is 'group'
-          currentGroup.sendNotification 'GroupStackTemplateRemoved', template._id
-
-        # UMUT FIX THE LINE BELOW - SY
-        reactor.dispatch actions.REMOVE_STACK, template._id
-        # UMUT FIX THE LINE ABOVE - SY
-        router.handleRoute '/IDE'
-        modal.destroy()
-        Tracker.track Tracker.STACKS_DELETE_TEMPLATE
+

     modal = kd.ModalView.confirm
       title       : title


StackEditor: use existing config content from stack template if possible

--- client/stack-editor/lib/editor/index.coffee
+++ client/stack-editor/lib/editor/index.coffee
@@ -680,8 +680,13 @@
         .add 'Following information will be fetched from variables section:'
         .add requiredData.custom

-    # Generate config data from parsed values
-    config = { requiredData, requiredProviders }
+    if stackTemplate?.config?
+      config = stackTemplate.config
+      config.requiredData = requiredData
+      config.requiredProviders = requiredProviders
+    else
+      # Generate config data from parsed values
+      config = { requiredData, requiredProviders }

     # Keep clone info if exists
     if clonedFrom = @stackTemplate?.config?.clonedFrom


Sliderbar Component: Minor codefix

Sliderbar Component: Minor codefix

--- components/sliderbar/KDSliderBarView.coffee
+++ components/sliderbar/KDSliderBarView.coffee
@@ -9,10 +9,10 @@
     options.snap       ?= yes
     data.handles

+    super options, data
+
     @handles            = []
     @labels             = []
-
-    super options, data

   createHandles:->
     for k, v of @getData().handles


Add More Choice Properties

Add More Choice Properties

--- src/coffee/experimental-controller.coffee
+++ src/coffee/experimental-controller.coffee
@@ -22,7 +22,7 @@
   constructor: (@title, @text, @choices) ->

 class Choice
-  constructor: (@text, @requirements, @nextStorylet) ->
+  constructor: (@name, @text, @seeReq, @chooseReq, @next) ->

 qbnApp.controller 'QBN', ($scope) ->
   $scope.qualities = [


Fix keywords

Fix keywords

--- src/commands/update/setDescription.coffee
+++ src/commands/update/setDescription.coffee
@@ -124,7 +124,7 @@
           document.querySelector("input[name=\"ListingModels[0].Listing.HardwareNotes.RecommendedHardwareNotesList[#{i}]\"]").value = output.hardware[i]

       if output.keywords?
-        for i in [0...11]
+        for i in [0...7]
           document.querySelector("input[name=\"AppListing.Keywords[#{i}]\"]").value = output.keywords[i]

       if output.trademark?


FIX: Group count when students move groups

--- src/app/common/services/group-service.coffee
+++ src/app/common/services/group-service.coffee
@@ -157,6 +157,7 @@
           # Get old group..
           grp = member.groupForGroupSet(group.groupSet())
           if grp?
+            grp.student_count -= 1
             # Remove current member from old group
             _.remove grp.members, (mbr) -> mbr.project_id == member.project_id
             _.remove member.groups, grp


MultiInput: Implemented method 'remove' to remove all its inputs to fix 'reload' method.

--- src/elements/MultiInput/MultiInput.coffee
+++ src/elements/MultiInput/MultiInput.coffee
@@ -49,6 +49,7 @@
 			return
 		for inp in @__inputs
 			inp.disable()
+		return

 	enable: ->
 		super()
@@ -56,6 +57,14 @@
 			return
 		for inp in @__inputs
 			inp.enable()
+		return
+
+	remove: ->
+		if @__inputs
+			for input in @__inputs
+				input.destroy()
+			delete @__inputs
+		return super()

 	initValue: ->
 		if CUI.util.isNull(v = @__data[@_name])


:lipstick:

:lipstick:

--- src/extensions/tree-view/tree-view.coffee
+++ src/extensions/tree-view/tree-view.coffee
@@ -19,6 +19,9 @@

     rootView.horizontal.prepend(@treeView)

+  @serialize: ->
+    @treeView.serialize()
+
   @content: (rootView) ->
     @div class: 'tree-view', tabindex: -1, =>
       @subview 'root', new DirectoryView(directory: rootView.project.getRootDirectory(), isExpanded: true)
@@ -28,9 +31,6 @@
     treeView.root.deserializeEntryExpansionStates(state.directoryExpansionStates)
     treeView.selectEntryForPath(state.selectedPath)
     treeView
-
-  @serialize: ->
-    @treeView.serialize()

   root: null


SpecHelper.mousedownInCenterOf now accepts a hash of options.

--- spec/helpers/SpecHelper.coffee
+++ spec/helpers/SpecHelper.coffee
@@ -7,12 +7,18 @@
       y: Math.floor(offset.top + element.outerHeight() / 2 - $document.scrollTop())
     }

-  @mouseDownInCenterOf: (element) ->
+  @mouseDownInCenterOf: (element, options = {}) ->
     center = @findCenterOf element
-    element.simulate 'mousedown',
+    element.simulate 'mousedown', @applyDefaults options,
       clientX: center.x
       clientY: center.y
     center

   @isNumber: (obj) -> (obj is +obj) or toString.call(obj) is '[object Number]'
   @isNaN: (obj) -> @isNumber(obj) and window.isNaN(obj)
+  @applyDefaults: (obj, sources...) ->
+    for source in sources
+      continue unless source
+      for prop of source
+        obj[prop] = source[prop] if obj[prop] is undefined
+    obj


allow USSD flows to start someone else in a flow

--- static/coffee/flows/services.coffee
+++ static/coffee/flows/services.coffee
@@ -432,7 +432,7 @@
         { type:'lang', name:'Set Language', verbose_name:'Set language for contact', icon: 'icon-language', filter:ALL }
         { type:'channel', name:'Set Channel', verbose_name:'Set preferred channel', icon: 'icon-phone', filter:[TEXT, VOICE] }
         { type:'flow', name:'Start Another Flow', verbose_name:'Start another flow', icon: 'icon-tree', flows:true, filter:[TEXT,VOICE] }
-        { type:'trigger-flow',   name:'Start Someone in a Flow', verbose_name:'Start someone else in a flow', icon: 'icon-tree', flows:true, filter:[TEXT,VOICE] }
+        { type:'trigger-flow',   name:'Start Someone in a Flow', verbose_name:'Start someone else in a flow', icon: 'icon-tree', flows:true, filter:[TEXT,VOICE,USSD] }
       ]

       @rulesets = [


more specific css selector for TERP iframe replacement, can also still do configurable setting if needed

--- src/scripts/modules/media/body/body.coffee
+++ src/scripts/modules/media/body/body.coffee
@@ -39,7 +39,7 @@
       MathJax?.Hub.Queue(['Typeset', MathJax.Hub], @$el.get(0))

       # Converts a TERP link to an OST-hosted iframe
-      @$el.find('a[href*="terp-"]').each () ->
+      @$el.find('a[href*="#terp-"]').each () ->
         terpCode = $(this).attr('href').match(/#terp\-(.*)/)[1]
         $(this).replaceWith("<iframe class='terp'
                                      src='https://stormy-wave-8747.herokuapp.com/terp/#{terpCode}/quiz_start'


Updated signature test

Updated signature test

--- test/all/signature.coffee
+++ test/all/signature.coffee
@@ -24,6 +24,10 @@

     expect(udefine).to.have.property('env')
     expect(envProp).to.be.a('object')
+
+  it 'udefine has a property .autoInject', ->
+    expect(udefine).to.have.property('autoInject')
+    expect(udefine.autoInject).to.be.a('boolean')

   it 'udefine has module property', ->
     expect(udefine).to.have.property('modules')