Create release tasks

Create release tasks

--- server-ce/Gruntfile.coffee
+++ server-ce/Gruntfile.coffee
@@ -100,6 +100,9 @@
 				done = @async()
 				Helpers.updateService(service, done)
 			grunt.registerTask "run:#{service.name}", "Run the ShareLaTeX #{service.name} service", ["bunyan", "execute:#{service.name}"]
+			grunt.registerTask "release:#{service.name}", "Create a new release version of #{service.name} (specify with --release option)", () ->
+				done = @async()
+				Helpers.createNewRelease(service, grunt.option("release"), done)

 	grunt.registerTask 'install:config', "Copy the example config into the real config", () ->
 		Helpers.installConfig @async()
@@ -139,7 +142,6 @@
 		Helpers.buildDeb @async()
 	grunt.registerTask "build:upstart_scripts", "Create upstart scripts for each service", () ->
 		Helpers.buildUpstartScripts()
-

 	Helpers =
 		installService: (service, callback = (error) ->) ->
@@ -183,7 +185,25 @@
 				proc = spawn "git", ["pull"], cwd: dir, stdio: "inherit"
 				proc.on "close", () ->
 					callback()
-
+
+		createNewRelease: (service, version, callback = (error) ->) ->
+			dir = service.name
+			proc = spawn "sed", [
+				"-i", "",
+				"s/\"version\".*$/\"version\": \"#{version}\",/g",
+				"package.json"
+			], cwd: dir, stdio: "inherit"
+			proc.on "close", () ->
+				proc = spawn "git", ["commit", "-a", "-m", "Release version #{version}"], cwd: dir, stdio: "inherit"
+				proc.on "close", () ->
+					proc = spawn "git", ["tag", "v#{version}"], cwd: dir, stdio: "inherit"
+					proc.on "close", () ->
+						proc = spawn "git", ["push"], cwd: dir, stdio: "inherit"
+						proc.on "close", () ->
+							proc = spawn "git", ["push", "--tags"], cwd: dir, stdio: "inherit"
+							proc.on "close", () ->
+								callback()
+
 		installNpmModules: (service, callback = (error) ->) ->
 			dir = service.name
 			proc = spawn "npm", ["install"], stdio: "inherit", cwd: dir


Print robot adapter

Print robot adapter

--- scripts/poo-tracker.coffee
+++ scripts/poo-tracker.coffee
@@ -6,7 +6,7 @@
 redis_client.auth info.auth.split(":")[1] if info.auth

 module.exports = (robot) ->
-  console.log("Poop boot is #{robot.constructor.name}")
+  console.log("Poop boot is #{robot.constructor.name} adapter: #{robot.adapter}")
   POO_TRACKER_KEY = "poops"
   POO_LATEST_KEY = "poops:latest_message"


Fixed display of errors

Fixed display of errors

--- utils.coffee
+++ utils.coffee
@@ -9,7 +9,7 @@

 module.exports = class Utils
   @adapters:
-    bbCallback: (callback) -> return {success: ((model) -> callback(null, model)), error: (-> callback(new Error("failed")))}
+    bbCallback: (callback) -> return {success: ((model) -> callback(null, model)), error: ((model, err) -> callback(err or new Error("Backbone call failed")))}

   # parse an object whose values are still JSON stringified
   @parse: (query) ->


fix for running tests on Windows

fix for running tests on Windows

--- test/ace/operators.coffee
+++ test/ace/operators.coffee
@@ -29,7 +29,7 @@
   eq @jim.registers['"'], "_.sortBy = function(obj, "

   @press 'yj'
-  eq @jim.registers['"'], """
+  eq @jim.registers['"'].replace(/\r\n/gm, "\n"), """
     _.sortBy = function(obj, iterator, context) {
       return _.pluck(_.map(obj, function(value, index, list) {


quest-page view: more consistent method naming

--- www/src/views/quest/page.coffee
+++ www/src/views/quest/page.coffee
@@ -17,7 +17,7 @@
             "click .quest-page-action .resurrect": "resurrect"
             "click .quest-page-action .reopen": "reopen"
             "click .invite": "inviteDialog"
-            "click .uninvite": "uninviteAction"
+            "click .uninvite": "uninvite"
             "click .join": "joinAction"
             "click .like": -> @model.like()
             "click .unlike": -> @model.unlike()
@@ -73,21 +73,20 @@
             @model.invite @$("#inputInvitee").val()  if e.keyCode is 13
             return

-        uninviteAction: (e) ->
+        uninvite: (e) ->
             @model.uninvite $(e.target).parent().attr("data-login")
-
-        joinAction: ->
-            @model.join()

         close: ->
             @model.close()
             modal = new QuestCompleted(model: @model)
             modal.start()

+        reopen: -> @model.reopen()
+
         abandon: -> @model.abandon()
+        resurrect: -> @model.resurrect()
         leave: -> @model.leave()
-        resurrect: -> @model.resurrect()
-        reopen: -> @model.reopen()
+        join: -> @model.join()

         serialize: ->
             params = super


:fire: mobile browser detection

:fire: mobile browser detection


added support for multiple codes for encounter diagnosis

--- app/assets/javascripts/encounter.js.coffee
+++ app/assets/javascripts/encounter.js.coffee
@@ -93,9 +93,14 @@
   transferFrom: -> hQuery.createCodedValue @json['transferFrom']

   ###*
-  @returns {CodedValue}
+  @returns {CodedValue|CodedValue[]}
   ###
-  diagnosis: -> hQuery.createCodedValue @json['diagnosis']
+  diagnosis: ->
+    if @json['diagnosis']?['codes']
+      # Support for diagnosis with multiple codes
+      hQuery.createCodedValues @json['diagnosis']['codes']
+    else
+      hQuery.createCodedValue @json['diagnosis']

   ###*
   @returns {CodedValue|CodedValue[]}


fix buggy radio button checking logic

fix buggy radio button checking logic

--- app/assets/javascripts/hat-trick.js.coffee
+++ app/assets/javascripts/hat-trick.js.coffee
@@ -139,7 +139,8 @@
     @form.formwizard("next")

   # TODO: Try linking to the same step rather than cloning it.
-  # I'm becoming more and more convinced that that won't work, however. And this isn't as bad as it used to be.
+  # I'm becoming more and more convinced that that won't work, however.
+  # And this isn't as bad as it used to be.
   repeatStep: (step) ->
     if $("fieldset##{step.name}").length is 0
       $sourceStep = this.findStep(step.repeatOf.fieldset)
@@ -226,9 +227,28 @@
     this.setFieldValues model, "input:checkbox", ($checkbox, value) =>
       $checkbox.attr("checked", "checked") if value

+  # TODO: DRY this up as much as possible. Radio buttons a little different
+  #       than the other form controls since they share names and behave as a
+  #       named group.
   setRadioButtons: (model) ->
-    this.setFieldValues model, "input:radio", ($radio, value) =>
-      $radio.find("[value=\"#{value}\"]").attr("checked", "checked")
+    $currentStep = this.currentStep()
+    selector = "input:radio"
+    radioGroups = {}
+    $currentStep.find(selector).each ->
+      radioGroups[$(this).attr("name")] = true
+    for radioGroup of radioGroups
+      do (radioGroup) =>
+        if radioGroup.search(@fieldRegex) isnt -1
+          log "Setting values of radio group #{radioGroup}"
+          [_, modelName, fieldName] = radioGroup.match(@fieldRegex)
+          log "modelName: #{modelName}; model[#{fieldName}]: #{model[fieldName]}"
+          if model['__name__'] is modelName and model[fieldName]?
+            fieldValue = model[fieldName]
+            $radioGroup = $("input:radio[name=\"#{radioGroup}\"]")
+            log "Removing checked attr from radio group #{radioGroup}"
+            $radioGroup.removeAttr("checked")
+            log "Checking button with value #{fieldValue}"
+            $radioGroup.filter("[value=\"#{fieldValue}\"]").attr("checked", "checked")

   setFormFields: (model) ->
     # log "Setting form fields based on: #{JSON.stringify(model)}"


Fix handling of OSC seq terminated by ESC\

--- app/assets/javascripts/player/vt.js.coffee
+++ app/assets/javascripts/player/vt.js.coffee
@@ -10,7 +10,7 @@
       @handleControlCharacter(data[1])
       return 2

-    else if match = data.match(/^(\x1b\x5d|\x9d).*?(\x1b\\\\|\x9c|\x07)/)
+    else if match = data.match(/^(\x1b\x5d|\x9d).*?(\x1b\\|\x9c|\x07)/)
       # OSC seq
       return match[0].length


Update router to support route parameters -- (courtesy of Byron)

--- src/coffee/cilantro/router.coffee
+++ src/coffee/cilantro/router.coffee
@@ -24,40 +24,40 @@
                 # removing them works..
                 Backbone.$(@options.main).children().remove()

-        _unloadAll: =>
-            @_unload(@_registered[id], false) for id in @_loaded.slice()
+        _unloadAll: (args...) =>
+            @_unload(@_registered[id], false, args...) for id in @_loaded.slice()
             return

-        _loadAll: =>
-            if not (ids = @_routes[Backbone.history.fragment])? then return
-            @_load(@_registered[id]) for id in ids
+        _loadAll: (route, args...) =>
+            if not (ids = @_routes[route])? then return
+            @_load(@_registered[id], args...) for id in ids
             return

-        _unload: (route, force=true) =>
+        _unload: (route, force=true, args...) =>
             if route.route? or force and (idx = @_loaded.indexOf(route.id)) >= 0
                 @_loaded.splice(idx, 1)
                 if (view = route._view)?
                     view?.$el.hide()
-                    view.trigger?('router:unload', @, Backbone.history.fragment)
+                    view.trigger?('router:unload', @, Backbone.history.fragment, args...)

-        _load: (options) =>
+        _load: (options, args...) =>
             # If the view has not be loaded before, check if it's a
             # module string and loader asynchronously
             if not options._view?
                 if _.isString options.view
                     require [options.view], (View) =>
                         options._view = new View options.options
-                        @_render(options)
+                        @_render(options, args...)
                         @_loaded.push(options.id)
                     , (err) ->
                         logger.error(err)
                     return
                 options._view = options.view

-            @_render(options)
+            @_render(options, args...)
             @_loaded.push(options.id)

-        _render: (options) =>
+        _render: (options, args...) =>
             view = options._view
             if not view._rendered
                 view._rendered = true
@@ -71,7 +71,7 @@
                     view.render?()

             view.$el.show()
-            view.trigger?('router:load', @, Backbone.history.fragment)
+            view.trigger?('router:load', @, Backbone.history.fragment, args...)
             return

         _register: (options) ->
@@ -86,9 +86,9 @@
                 @_load(options)
             else if not @_handlers[options.route]?
                 @_routes[options.route] = []
-                @_handlers[options.route] = handler = =>
-                    @_unloadAll()
-                    @_loadAll()
+                @_handlers[options.route] = handler = (args...) =>
+                    @_unloadAll(args...)
+                    @_loadAll(options.route, args...)
                     return
                 @route options.route, options.id, handler
             if options.route?


ensure mapped paths do exist and permissions are set to 0777

--- src/coffee/compose/actions.coffee
+++ src/coffee/compose/actions.coffee
@@ -29,6 +29,20 @@
     env = buildEnv config, data

     emitLogCb = (data) -> eventEmitter.emit 'startup-log', data.toString()
+
+    volumePaths = []
+    for service in _.values composition.services when service.volumes
+      volumePaths = _.uniq (_.union volumePaths, service.volumes).map (mapping) ->
+        mapping.split(':')[0]
+
+    for path in volumePaths
+      try
+        process.umask 0
+        mkdirp.sync path
+        fs.chmodSync path, 0o777
+        console.log 'Created volume', path
+      catch err
+        console.log('Error while creating volume dir', err) unless err.code is 'EEXIST'

     ensureMkdir scriptDir, ->
       writeFile scriptPath, compose, ->
@@ -86,7 +100,7 @@
     unless not err or err.code is 'EEXIST'
       console.log 'Cannot make dir', scriptDir, err
     else
-      success()
+      success?()

 writeFile = (path, contents, success) ->
   fs.writeFile path, contents, (err) ->


Tira o closure redundante

Tira o closure redundante

--- src/coffee/portal-minicart.coffee
+++ src/coffee/portal-minicart.coffee
@@ -1,240 +1,239 @@
-(($, window, document) ->
-
-  pluginName = 'vtexMinicart'
-  defaults = {
-    timeoutToHide: null
-    cartData: null
-    $template: null
-  }
-
-  class vtexMinicart
-    constructor: (@element, options) ->
-      @options = $.extend {}, defaults, options
-
-      @_defaults = defaults
-      @_name = pluginName
-
-      @init()
-
-    init: ->
-      self = this
-
-      self.options.$template = $ """
-

-

-

-

-

-

-

-                            Produto
-                            Preço
-                            Quantidade
-                            Excluir
-

-

-

-

-

-

-

-                    Total\
-
-                        R$ 0
-
-

-
-

-

-

-

-      """
-
-      $(self.element).after self.options.$template
-
-      $(self.options.$template)
-      .mouseover ->
-        $(window).trigger "miniCartMouseOver"
-      .mouseout ->
-        $(window).trigger "miniCartMouseOut"
-
-      $(window).on "miniCartMouseOver", ->
-        if self.options.cartData?.items.length > 0
-          $(".vtexsc-cart").slideDown()
-          clearTimeout self.options.timeoutToHide
-
-      $(window).on "miniCartMouseOut", ->
-        clearTimeout self.options.timeoutToHide
-        self.options.timeoutToHide = setTimeout ->
-          $(".vtexsc-cart").stop(true, true).slideUp()
-        , 800
-
-      $(window).on "cartUpdated", (event, cartData, show) ->
-        if cartData?.items? and cartData.items.length is 0
-          $(".vtexsc-cart").slideUp()
-          return
-        if show
-          $(".vtexsc-cart").slideDown()
-          self.options.timeoutToHide = setTimeout ->
-            $(".vtexsc-cart").stop(true, true).slideUp()
-          , 3000
-
-      $(window).on 'productAddedToCart', ->
-        promiseAdd = self.getData()
-        promiseAdd.success (data) ->
-          self.updateItems data
-          self.changeCartValues data
-          $(window).trigger "cartUpdated", [null, true]
-
-      promise = @getData()
-      promise.success (data) ->
-        self.insertCartItems data
-        self.changeCartValues data
-
-
-    getData: ->
-      self = this
-
-      promise = $.ajax {
-        url: "/api/checkout/pub/orderForm/"
-        data: JSON.stringify(expectedOrderFormSections: ["items", "paymentData", "totalizers"])
-        dataType: "json"
-        contentType: "application/json; charset=utf-8"
-        type: "POST"
-      }
-
-      promise.done (data) ->
-        self.options.cartData = data
-
-      promise.fail (jqXHR, textStatus, errorThrown) ->
-        # console.log "Error Message: " + textStatus
-        # console.log "HTTP Error: " + errorThrown
-
-      promise
-
-    insertCartItems: (data) ->
-      self = this
-
-      if data
-        total = 0
-
-        for subtotal in data.totalizers
-          total += subtotal.value if subtotal.id is 'Items'
-
-        $('.vtexsc-text', self.options.$template).text 'R$' + self.formatCurrency(total)
-
-        self.updateItems data
-
-    deleteItem: (item) ->
-      self = this
-
-      $(item).parent().find('.vtexsc-overlay').show()
-
-      data = JSON.stringify(
-        expectedOrderFormSections: ["items", "paymentData", "totalizers"]
-        orderItems: [
-          index: $(item).data("index")
-          quantity: 0
-        ]
-      )
-
-      promise = $.ajax {
-        url: "/api/checkout/pub/orderForm/" + self.options.cartData.orderFormId + "/items/update/"
-        data: data
-        dataType: "json"
-        contentType: "application/json; charset=utf-8"
-        type: "POST"
-      }
-
-      promise.success (data) ->
-        self.options.cartData = data
-        self.changeCartValues data
-        self.updateItems data
-        $(window).trigger "cartUpdated", [data]
-
-      promise.done ->
-        $(item).parent().find('.vtexsc-overlay').hide()
-
-      promise.fail (jqXHR, textStatus, errorThrown) ->
-        # console.log "Error Message: " + textStatus
-        # console.log "HTTP Error: " + errorThrown
-
-    updateItems: (data) ->
-      self = this
-      $template = self.options.$template
-
-      if data
-        items = ''
-
-        $(".vtexsc-productList tbody", $template).html ""
-
-        for item, i in data.items
-          item = """
-

-
-
-
-
-
"#{item.name}"

-
-
-

-                      R$ #{self.formatCurrency(item.price)}
-

-
-
-

-                      #{item.quantity}
-

-
-
-
-                      excluir
-
-

-
-

-          """
-          items += item
-
-        $(".vtexsc-productList tbody", $template).html items
-
-        $(".vtexsc-productList .cartSkuRemove", $template).click ->
-          self.deleteItem(this)
-
-    changeCartValues: (data) ->
-      self = this;
-
-      if data
-        total = 0
-        for subtotal in data.totalizers
-          total += subtotal.value if subtotal.id is 'Items'
-
-        $(".vtexsc-text", self.options.$template).text "R$ " + self.formatCurrency total
-
-    formatCurrency: (value) ->
-      if value is '' or not value? or isNaN value
-        num = 0.00
-      else
-        num = value / 100
-      parseFloat(num).toFixed(2).replace('.', ',').toString().replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1.')
-
-    showMinicart: (value) ->
-      promise = @getData()
-      promise.done ->
-        self.updateItems data
-        $(".vtexsc-cart").slideDown()
-        clearTimeout @options.timeoutToHide
-        @options.timeoutToHide = setTimeout ->
-          $(".vtexsc-cart").slideUp()
-        , 3000
-
-    $.fn[pluginName] = (options) ->
-      @each ->
-        if !$.data(this, "plugin_#{pluginName}")
-          $.data(@, "plugin_#{pluginName}", new vtexMinicart(@, options))
-
-)( jQuery, window, document )
+$ = window.jQuery
+
+pluginName = 'vtexMinicart'
+defaults = {
+	timeoutToHide: null
+	cartData: null
+	$template: null
+}
+
+class vtexMinicart
+	constructor: (@element, options) ->
+		@options = $.extend {}, defaults, options
+
+		@_defaults = defaults
+		@_name = pluginName
+
+		@init()
+
+	init: ->
+		self = this
+
+		self.options.$template = $ """
+

+

+

+

+

+

+

+													Produto
+													Preço
+													Quantidade
+													Excluir
+

+

+

+

+

+

+

+									Total\
+
+											R$ 0
+
+

+
+

+

+

+

+		"""
+
+		$(self.element).after self.options.$template
+
+		$(self.options.$template)
+		.mouseover ->
+			$(window).trigger "miniCartMouseOver"
+		.mouseout ->
+			$(window).trigger "miniCartMouseOut"
+
+		$(window).on "miniCartMouseOver", ->
+			if self.options.cartData?.items.length > 0
+				$(".vtexsc-cart").slideDown()
+				clearTimeout self.options.timeoutToHide
+
+		$(window).on "miniCartMouseOut", ->
+			clearTimeout self.options.timeoutToHide
+			self.options.timeoutToHide = setTimeout ->
+				$(".vtexsc-cart").stop(true, true).slideUp()
+			, 800
+
+		$(window).on "cartUpdated", (event, cartData, show) ->
+			if cartData?.items? and cartData.items.length is 0
+				$(".vtexsc-cart").slideUp()
+				return
+			if show
+				$(".vtexsc-cart").slideDown()
+				self.options.timeoutToHide = setTimeout ->
+					$(".vtexsc-cart").stop(true, true).slideUp()
+				, 3000
+
+		$(window).on 'productAddedToCart', ->
+			promiseAdd = self.getData()
+			promiseAdd.success (data) ->
+				self.updateItems data
+				self.changeCartValues data
+				$(window).trigger "cartUpdated", [null, true]
+
+		promise = @getData()
+		promise.success (data) ->
+			self.insertCartItems data
+			self.changeCartValues data
+
+
+	getData: ->
+		self = this
+
+		promise = $.ajax {
+			url: "/api/checkout/pub/orderForm/"
+			data: JSON.stringify(expectedOrderFormSections: ["items", "paymentData", "totalizers"])
+			dataType: "json"
+			contentType: "application/json; charset=utf-8"
+			type: "POST"
+		}
+
+		promise.done (data) ->
+			self.options.cartData = data
+
+		promise.fail (jqXHR, textStatus, errorThrown) ->
+			# console.log "Error Message: " + textStatus
+			# console.log "HTTP Error: " + errorThrown
+
+		promise
+
+	insertCartItems: (data) ->
+		self = this
+
+		if data
+			total = 0
+
+			for subtotal in data.totalizers
+				total += subtotal.value if subtotal.id is 'Items'
+
+			$('.vtexsc-text', self.options.$template).text 'R$' + self.formatCurrency(total)
+
+			self.updateItems data
+
+	deleteItem: (item) ->
+		self = this
+
+		$(item).parent().find('.vtexsc-overlay').show()
+
+		data = JSON.stringify(
+			expectedOrderFormSections: ["items", "paymentData", "totalizers"]
+			orderItems: [
+				index: $(item).data("index")
+				quantity: 0
+			]
+		)
+
+		promise = $.ajax {
+			url: "/api/checkout/pub/orderForm/" + self.options.cartData.orderFormId + "/items/update/"
+			data: data
+			dataType: "json"
+			contentType: "application/json; charset=utf-8"
+			type: "POST"
+		}
+
+		promise.success (data) ->
+			self.options.cartData = data
+			self.changeCartValues data
+			self.updateItems data
+			$(window).trigger "cartUpdated", [data]
+
+		promise.done ->
+			$(item).parent().find('.vtexsc-overlay').hide()
+
+		promise.fail (jqXHR, textStatus, errorThrown) ->
+			# console.log "Error Message: " + textStatus
+			# console.log "HTTP Error: " + errorThrown
+
+	updateItems: (data) ->
+		self = this
+		$template = self.options.$template
+
+		if data
+			items = ''
+
+			$(".vtexsc-productList tbody", $template).html ""
+
+			for item, i in data.items
+				item = """
+

+
+
+
+
+
"#{item.name}"

+
+
+

+										R$ #{self.formatCurrency(item.price)}
+

+
+
+

+										#{item.quantity}
+

+
+
+
+										excluir
+
+

+
+

+				"""
+				items += item
+
+			$(".vtexsc-productList tbody", $template).html items
+
+			$(".vtexsc-productList .cartSkuRemove", $template).click ->
+				self.deleteItem(this)
+
+	changeCartValues: (data) ->
+		self = this;
+
+		if data
+			total = 0
+			for subtotal in data.totalizers
+				total += subtotal.value if subtotal.id is 'Items'
+
+			$(".vtexsc-text", self.options.$template).text "R$ " + self.formatCurrency total
+
+	formatCurrency: (value) ->
+		if value is '' or not value? or isNaN value
+			num = 0.00
+		else
+			num = value / 100
+		parseFloat(num).toFixed(2).replace('.', ',').toString().replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1.')
+
+	showMinicart: (value) ->
+		promise = @getData()
+		promise.done ->
+			self.updateItems data
+			$(".vtexsc-cart").slideDown()
+			clearTimeout @options.timeoutToHide
+			@options.timeoutToHide = setTimeout ->
+				$(".vtexsc-cart").slideUp()
+			, 3000
+
+	$.fn[pluginName] = (options) ->
+		@each ->
+			if !$.data(this, "plugin_#{pluginName}")
+				$.data(@, "plugin_#{pluginName}", new vtexMinicart(@, options))
+

 $ -> $('.portal-minicart-ref').vtexMinicart()
 # $ -> $('#vtex-minicart').vtexMinicart()


update total points when deleting a day of the sprint

--- src/project/directives/resources-by-day/controller.coffee
+++ src/project/directives/resources-by-day/controller.coffee
@@ -18,3 +18,4 @@
       _.remove $scope.days, day
       $scope.matrix.splice index, 1
     $scope.selected = []
+    $scope.onUpdate()


fix TZ handling server-side

fix TZ handling server-side

--- server/models/alarm.coffee
+++ server/models/alarm.coffee
@@ -28,13 +28,20 @@

 # before saving
 # take an attributes object
-# if the object has a TZ, the trigg is considered to be in this TZ
-# else we use the User's TZ
 # set the trigg to UTC
 # store the TZed trigg in timezoneHour
 Alarm.toUTC = (attrs) ->
-    attrs.timezone = attrs.timezone or User.timezone
-    trigg = new time.Date(attrs.trigg, attrs.timezone)
+    if attrs.timezoneHour # popover
+        trigg = new time.Date(attrs.trigg, User.timezone)
+        trigg.setTimezone(attrs.timezone)
+        unless trigg.toString().slice(16, 21) is attrs.timezoneHour
+            [hours, minutes] = attrs.timezoneHour.split(':')
+            trigg.setHours(hours)
+            trigg.setMinutes(minutes)
+    else # D&D in the interface
+        trigg = new time.Date(attrs.trigg, User.timezone)
+        trigg.setTimezone(attrs.timezone)
+
     attrs.timezoneHour = trigg.toString().slice(16, 21)
     trigg.setTimezone('UTC')
     attrs.trigg = trigg.toString().slice(0, 24)


Fix issue with message to 'all'

Fix issue with message to 'all'

--- server/publications.coffee
+++ server/publications.coffee
@@ -4,10 +4,10 @@
 Meteor.publish 'projectMessages', (projectId) ->
     check projectId, String
     # Find all messages for the current project which have our user as sender or recipient
-    Messages.find {$and: [{$or: [{recipients: {$in: [this.userId]}}, {sender: this.userId}]}, {project: projectId}]}
+    Messages.find({$and: [{$or: [{recipients: {$in: [this.userId]}}, {recipients: {$size: 0}}, {sender: this.userId}]}, {project: projectId}]}, {sort: { sent: 1}})

 Meteor.publish 'allMessages', ->
-    Messages.find {$or: [{recipients: {$in: [this.userId]}}, {sender: this.userId}]}
+    Messages.find({$or: [{recipients: {$in: [this.userId]}}, {recipients: {$size: 0}}, {sender: this.userId}]}, {sort: {sent: 1}})

 Meteor.publish 'usernames', ->
     Meteor.users.find {}, {fields: {'emails': 1, '_id': 1, 'telephone_nr': 1, 'skype_id': 1, 'name': 1}}


Remove unnecessary codes

Remove unnecessary codes

--- server/publications.coffee
+++ server/publications.coffee
@@ -11,7 +11,7 @@
 Meteor.publish "dinners", ->
   Dinners.find()

-Meteor.publish 'flowers', (selector, options) ->
+Meteor.publish 'flowers', ->
   Flowers.find()

 Meteor.publish 'availableFriends', (selector, options) ->


Updated AMD tests

Updated AMD tests

--- test/core/test-amd.coffee
+++ test/core/test-amd.coffee
@@ -22,4 +22,4 @@
   require ['underscore', 'backbone', 'knockout', 'knockback', 'knockback-inspector', 'qunit_test_runner'], (_, Backbone, ko, kb, kbi, runner) ->
     window._ = window.Backbone = window.ko = window.kbi = null # force each test to require dependencies synchronously
     # window.kb = null # force each test to require dependencies synchronously
-    require ['./build/test'], -> runner.start()+    runner.start(); require ['./build/test'], ->


shrink message size for test to help iojs

--- test/consumer.test.coffee
+++ test/consumer.test.coffee
@@ -234,7 +234,7 @@

   it 'test we can consume a queue and get a big message 588', (done)->

-    testData = new Buffer(MaxFrameSize*3.5)
+    testData = new Buffer(MaxFrameSize*1.5)
     amqp = null
     queue = uuid()


Add descriptions to the async spec.

Add descriptions to the async spec.

--- src/Witness/Scripts/specs/dsl/async.coffee
+++ src/Witness/Scripts/specs/dsl/async.coffee
@@ -1,26 +1,26 @@
 describe "async",
 {
-	given: ->
+	"given the DSL is activated": ->
 		@target = {}
 		@dsl = new Witness.Dsl @target
 		@dsl.activate()

-	when: ->
+	"when async is called with a function argument": ->
 		@func = @target.async (->)

-	then:
+	"then the function has an async property added":
 		func: async: should.notBe undefined
 },
 {
-	given: ->
+	"given the DSL is activated": ->
 		@target = {}
 		@dsl = new Witness.Dsl @target
 		@dsl.activate()

-	when: ->
+	"when async is called with function and timeout of 2000 milliseconds": ->
 		# specify the timeout
 		@func = @target.async (->), 2000

-	then:
+	"then the timeout is stored":
 		func: async: timeout: should.be 2000
-}+}


Adds runtime error test

Adds runtime error test

--- test/database-spec.coffee
+++ test/database-spec.coffee
@@ -14,9 +14,9 @@
       assert.equal db, null
       done()

-  it 'should return error if database does not exist', (done) ->
+  it.skip 'should return error if database does not exist', (done) ->
     database.connect 'foo', (err, db) ->
-      console.log db
+      #console.log db
       #assert.fail()
       done()

@@ -27,3 +27,12 @@
       assert.equal db.databaseName, 'ntb_07', 'database should be ntb_07'
       done()

+  it 'should not suprepress runtime errors', (done) ->
+    database.connect null, (err, db) ->
+      try
+        foobar()
+      catch e
+        assert e instanceof Error, 'e should be an instance of error'
+        assert /ReferenceError: foobar is not defined/.test(e), 'e should be ReferenceError'
+        done()
+


Update FacebookHandler test

Update FacebookHandler test

--- test/app/core/social-handlers/FacebookHandler.spec.coffee
+++ test/app/core/social-handlers/FacebookHandler.spec.coffee
@@ -69,7 +69,7 @@
     expect(FB.api).toHaveBeenCalled()
     apiArgs = FB.api.calls.argsFor(0)
     expect(apiArgs[0]).toBe('/me')
-    apiArgs[1](mockMe) # sending the 'response'
+    apiArgs[2](mockMe) # sending the 'response'
     request = jasmine.Ajax.requests.mostRecent()
     expect(request).toBeDefined()
     params = JSON.parse request.params


Fix some Node.js v0.11.x test failures

Fix some Node.js v0.11.x test failures

--- test/helpers.coffee
+++ test/helpers.coffee
@@ -13,6 +13,9 @@

 AWS = require('../lib/aws')
 EventEmitter = require('events').EventEmitter
+
+require('util').print = (data) ->
+  process.stdout.write(data)

 # Mock credentials
 AWS.config.update
@@ -75,7 +78,7 @@
   cb(httpResp)

   AWS.util.arrayEach data.slice(), (str) ->
-    if httpResp._events.readable
+    if httpResp._events.readable || process.version > 'v0.11.3'
       httpResp.emit('readable')
     else
       httpResp.emit('data', new Buffer(str))


fixed #75: error when ⍺↓scalar is empty

fixed #75: error when ⍺↓scalar is empty

--- src/vocabulary/drop.coffee
+++ src/vocabulary/drop.coffee
@@ -14,12 +14,16 @@
       # ⍬↓3 3⍴⍳9                 <=> 3 3⍴⍳9
       # 1 1↓ 2 3 4⍴"ABCDEFGHIJKLMNOPQRSTUVWXYZ"   <=> 1 2 4 ⍴ 'QRSTUVWX'
       # ¯1 ¯1↓ 2 3 4⍴"ABCDEFGHIJKLMNOPQRSTUVWXYZ" <=> 1 2 4 ⍴ 'ABCDEFGH'
+      # 1↓0                      <=> ⍬
+      # 0 1↓2                    <=> 1 0⍴0
+      # 1 2↓3                    <=> 0 0⍴0
+      # ⍬↓0                      <=> 0
       if alpha.shape.length > 1
         rankError()
       a = alpha.toArray()
       for x in a when not isInt x then domainError()
       if omega.shape.length is 0
-        omega = new APLArray omega.data, repeat([1], a.length), omega.stride, omega.offset
+        omega = new APLArray omega.data, repeat([1], a.length), repeat([0], a.length), omega.offset
       else
         if a.length > omega.shape.length
           rankError()


Move {div..} = to render method.

Move {div..} = to render method.

--- app/client/js/songs/edit/react/page.coffee
+++ app/client/js/songs/edit/react/page.coffee
@@ -1,5 +1,7 @@
 goog.provide 'app.songs.edit.react.Page'

+goog.require 'app.songs.Song'
+goog.require 'este.dom'
 goog.require 'goog.ui.Textarea'

 class app.songs.edit.react.Page
@@ -9,13 +11,14 @@
     @constructor
   ###
   constructor: (routes) ->
-    {div,form,input,textarea,button,a} = React.DOM

     @create = React.createClass

       render: ->
+        {div,form,input,textarea,button,a} = React.DOM
+
         div className: 'new-song',
-          form onSubmit: @onFormSubmit,
+          form onSubmit: @onFormSubmit, ref: 'form',
             input
               autoFocus: true
               name: 'name'
@@ -43,7 +46,18 @@

       onFormSubmit: (e) ->
         e.preventDefault()
-        @refs['name'].getDOMNode().focus()
+        @addSong()
+
+      addSong: ->
+        form = @refs['form'].getDOMNode()
+        data = este.dom.serializeForm form
+        song = new app.songs.Song data.name, data.chordpro
+        errors = song.validate()
+        if errors.length
+          field = form.elements[errors[0].prop]
+          field.focus()
+          return
+        routes.home.redirect()

   @MSG_SONG_NAME: goog.getMsg 'Song name'
   @MSG_WRITE_LYRICS_HERE: goog.getMsg 'Write lyrics here'


SplitComboView: Revert to its first state.

--- src/components/split/splitcomboview.coffee
+++ src/components/split/splitcomboview.coffee
@@ -1,19 +1,17 @@
 KDView      = require './../../core/view.coffee'
 KDSplitView = require './splitview.coffee'

-module.exports = class KDSplitComboView extends KDSplitView
+module.exports = class KDSplitComboView extends KDView

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

     options.cssClass or= "kdsplitcomboview"
-    options.sizes    or= ['100%', 0]

     super options, data

-    @once "viewAppended", @bound 'init'
+    @init options

-  init: ->
-    options = @getOptions()
+  init: (options) ->
     @addSubView @createSplitView options.direction, options.sizes, options.views

   createSplitView: (type, sizes, viewsConfig) ->


changed MongoWatcher -> MongoWatch

changed MongoWatcher -> MongoWatch

--- test/mongo-watcher.coffee
+++ test/mongo-watcher.coffee
@@ -1,9 +1,9 @@
 should = require 'should'
 {Server, Db} = require 'mongodb'

-MongoWatcher = require '../'
+MongoWatch = require '../'

-describe 'Mongo Watcher', ->
+describe 'Mongo Watch', ->

   before (done) ->
     client = new Db 'test', new Server('localhost', 27017), {w: 1}
@@ -19,7 +19,7 @@
     @users.remove {}, ->

   it 'insert should emit an event', (done) ->
-    @watcher = new MongoWatcher
+    @watcher = new MongoWatch
     @watcher.watch 'test.users', (event) ->

       if event.op is 'i'
@@ -31,7 +31,7 @@
       should.not.exist err

   it 'pretty format should work', (done) ->
-    @watcher = new MongoWatcher {format: 'pretty'}
+    @watcher = new MongoWatch {format: 'pretty'}
     @watcher.watch 'test.users', (event) ->

       if event.operation is 'insert'
@@ -50,7 +50,7 @@

       # And I'm watching the users collection
       counter = 0
-      @watcher = new MongoWatcher {format: 'pretty'}
+      @watcher = new MongoWatch {format: 'pretty'}
       @watcher.watch 'test.users', (event) ->

         # I should get three update events


Added new translations

Added new translations

--- app/locale/mk-MK.coffee
+++ app/locale/mk-MK.coffee
@@ -415,67 +415,67 @@
     returns: "Враќа"
     granted_by: "Овозможено од"

-#  save_load:
-#    granularity_saved_games: "Saved"
-#    granularity_change_history: "History"
-
-#  options:
-#    general_options: "General Options" # Check out the Options tab in the Game Menu while playing a level
-#    volume_label: "Volume"
-#    music_label: "Music"
-#    music_description: "Turn background music on/off."
-#    autorun_label: "Autorun"
-#    autorun_description: "Control automatic code execution."
-#    editor_config: "Editor Config"
-#    editor_config_title: "Editor Configuration"
-#    editor_config_level_language_label: "Language for This Level"
-#    editor_config_level_language_description: "Define the programming language for this particular level."
-#    editor_config_default_language_label: "Default Programming Language"
-#    editor_config_default_language_description: "Define the programming language you want to code in when starting new levels."
+  save_load:
+    granularity_saved_games: "Зачувани"
+    granularity_change_history: "Историја"
+
+  options:
+    general_options: "Општи подесувања" # Check out the Options tab in the Game Menu while playing a level
+    volume_label: "Јачина на звук"
+    music_label: "Музика"
+    music_description: "Вклучи/Исклучи позадинска музика."
+    autorun_label: "Автоматско извршување"
+    autorun_description: "Контрола на автоматско извршување на кодот."
+    editor_config: "Подесувања на едитор"
+    editor_config_title: "Подесувања на едитор"
+    editor_config_level_language_label: "Јазик за ова ниво"
+    editor_config_level_language_description: "Избери го програмскиот јазик за ова конкретно ниво."
+    editor_config_default_language_label: "Стандарден програмски јазик"
+    editor_config_default_language_description: "Избери го програмскиот јазик во кој сакаш да програмираш кога започуваш нови нивоа."
 #    editor_config_keybindings_label: "Key Bindings"
 #    editor_config_keybindings_default: "Default (Ace)"
-#    editor_config_keybindings_description: "Adds additional shortcuts known from the common editors."
-#    editor_config_livecompletion_label: "Live Autocompletion"
-#    editor_config_livecompletion_description: "Displays autocomplete suggestions while typing."
-#    editor_config_invisibles_label: "Show Invisibles"
-#    editor_config_invisibles_description: "Displays invisibles such as spaces or tabs."
-#    editor_config_indentguides_label: "Show Indent Guides"
-#    editor_config_indentguides_description: "Displays vertical lines to see indentation better."
-#    editor_config_behaviors_label: "Smart Behaviors"
-#    editor_config_behaviors_description: "Autocompletes brackets, braces, and quotes."
-
-#  about:
-#    why_codecombat: "Why CodeCombat?"
-#    why_paragraph_1: "If you want to learn to program, you don't need lessons. You need to write a lot of code and have a great time doing it."
-#    why_paragraph_2_prefix: "That's what programming is about. It's gotta be fun. Not fun like"
-#    why_paragraph_2_italic: "yay a badge"
-#    why_paragraph_2_center: "but fun like"
-#    why_paragraph_2_italic_caps: "NO MOM I HAVE TO FINISH THE LEVEL!"
-#    why_paragraph_2_suffix: "That's why CodeCombat is a multiplayer game, not a gamified lesson course. We won't stop until you can't stop--but this time, that's a good thing."
-#    why_paragraph_3: "If you're going to get addicted to some game, get addicted to this one and become one of the wizards of the tech age."
-#    press_title: "Bloggers/Press"
-#    press_paragraph_1_prefix: "Want to write about us? Feel free to download and use all of the resources included in our"
-#    press_paragraph_1_link: "press packet"
-#    press_paragraph_1_suffix: ". All logos and images may be used without contacting us directly."
-#    team: "Team"
-#    george_title: "CEO"
-#    george_blurb: "Businesser"
-#    scott_title: "Programmer"
-#    scott_blurb: "Reasonable One"
-#    nick_title: "Programmer"
-#    nick_blurb: "Motivation Guru"
-#    michael_title: "Programmer"
-#    michael_blurb: "Sys Admin"
-#    matt_title: "Programmer"
-#    matt_blurb: "Bicyclist"
-
-#  versions:
-#    save_version_title: "Save New Version"
-#    new_major_version: "New Major Version"
-#    cla_prefix: "To save changes, first you must agree to our"
-#    cla_url: "CLA"
-#    cla_suffix: "."
-#    cla_agree: "I AGREE"
+    editor_config_keybindings_description: "Додава дополнителни кратенки познати од вообичаените едитори."
+    editor_config_livecompletion_label: "Автоматско дополнување 'во живо'"
+    editor_config_livecompletion_description: "Прикажува предлози за автоматско дополнување/довршување на командите додека куцаш."
+    editor_config_invisibles_label: "Прикажи невидливи знаци"
+    editor_config_invisibles_description: "Ги прикажува невидливите знаци, како на пример знакот за празно место."
+    editor_config_indentguides_label: "Прикажи водичи за 'вдлабнување'"
+    editor_config_indentguides_description: "Прикажува вертикални линии за подобар преглед на 'вдлабнувањaтa' на текстот."
+    editor_config_behaviors_label: "Паметно однесување"
+    editor_config_behaviors_description: "Автоматски ги затвара отворените загради и наводници."
+
+  about:
+    why_codecombat: "Зошто CodeCombat?"
+    why_paragraph_1: "Ако сакаш да научиш да програмираш тоа не значи дека ти требаат предавања. Треба да напишеш многу код и да си поминеш одлично правејќи го тоа."
+    why_paragraph_2_prefix: "Тоа е суштината на програмирањето. Мора да биде забавно. Не забавно од типот"
+    why_paragraph_2_italic: "јеее значка"
+    why_paragraph_2_center: "туку забавно од типот"
+    why_paragraph_2_italic_caps: "НЕ МАМО, МОРАМ ДА ГО ЗАВРШАМ НИВОТО!"
+    why_paragraph_2_suffix: "Поради тоа CodeCombat е игра за повеќе играчи, а не низа игриви предавања. Нема да прекинеме се додека ти не можеш да прекинеш--ама овој пат, тоа е добра работа."
+    why_paragraph_3: "Ако веќе стануваш зависен од некоја игра, стани зависен од оваа и стани еден од волшебниците на технолошкото доба."
+    press_title: "Блогери/Печат"
+    press_paragraph_1_prefix: "Сакаш да пишуваш за нас? Слободно можеш да ги симнеш и користиш сите ресурси вклучени во нашиот"
+    press_paragraph_1_link: "пакет за печат"
+    press_paragraph_1_suffix: ". Сите логоа и слики можат да бидат користени без да не контактираш директно."
+    team: "Тим"
+    george_title: "(CEO) Извршен директор"
+    george_blurb: "Бизнисер"
+    scott_title: "Програмер"
+    scott_blurb: "Оној разумниот"
+    nick_title: "Програмер"
+    nick_blurb: "Мотивациски гуру"
+    michael_title: "Програмер"
+    michael_blurb: "Систем администратор"
+    matt_title: "Програмер"
+    matt_blurb: "Бициклист"
+
+  versions:
+    save_version_title: "Зачувај нова верзија"
+    new_major_version: "Нова поголема(major) верзија"
+    cla_prefix: "За да ги зачуваш промените, мораш да го прифатиш нашиот"
+    cla_url: "CLA"
+    cla_suffix: "(договор за тие што допринесуваат)." # Short explanation for CLA in macedonian
+    cla_agree: "ПРИФАЌАМ"

 #  contact:
 #    contact_us: "Contact CodeCombat"


Tag::watch() bug with changing emitted nodes fixed

--- Element/element/tag/query.coffee
+++ Element/element/tag/query.coffee
@@ -288,6 +288,8 @@

 	constructor: (node, queries) ->
 		super()
+		@_nodesToAdd = []
+		@_nodesToRemove = []
 		@node = node
 		@queries = queries
 		@uid = utils.uid()
@@ -388,16 +390,13 @@

 	checkWatchersDeeply: checkWatchersDeeply = do ->
 		pending = false
+		watchersWithNodesToAdd = []
+		watchersWithNodesToRemove = []

 		i = 0
 		CHECK_WATCHERS_THIS = 1 << i++
 		CHECK_WATCHERS_CHILDREN = 1 << i++
 		CHECK_WATCHERS_ALL = (1 << i++) - 1
-		CHECK_WATCHERS_SWITCH_1 = i << i++
-		CHECK_WATCHERS_SWITCH_2 = i << i++
-
-		currentWatchersSwitch = CHECK_WATCHERS_SWITCH_1
-		nextWatchersSwitch = CHECK_WATCHERS_SWITCH_2

 		checkRec = (watcher, watcherUid, node, update) ->
 			unless update & CHECK_WATCHERS_THIS
@@ -413,14 +412,16 @@

 					# add in watcher
 					watcher.nodes.push node
-					emitSignal watcher, 'onAdd', node
+					if watcher._nodesToAdd.push(node) is 1
+						watchersWithNodesToAdd.push watcher
 				else if inWatchers and inWatchers[watcherUid] and not watcher.test(node)
 					# remove from node
 					node._inWatchers[watcherUid] = false

 					# remove from watcher
 					utils.removeFromUnorderedArray watcher.nodes, node
-					emitSignal watcher, 'onRemove', node
+					if watcher._nodesToRemove.push(node) is 1
+						watchersWithNodesToRemove.push watcher

 			# check recursively
 			if update & CHECK_WATCHERS_CHILDREN and node instanceof Tag
@@ -430,12 +431,8 @@
 			return

 		clearRec = (node) ->
-			checkWatchers = node._checkWatchers
-			if checkWatchers > 0
-				if checkWatchers & currentWatchersSwitch
-					node._checkWatchers = checkWatchers & ~nextWatchersSwitch
-				else
-					node._checkWatchers = 0
+			if node._checkWatchers > 0
+				node._checkWatchers = 0
 				if node instanceof Tag
 					for child in node.children
 						clearRec child
@@ -462,7 +459,8 @@
 					# remove from watcher
 					nodes[i] = nodes[n-1]
 					nodes.pop()
-					emitSignal watcher, 'onRemove', node
+					if watcher._nodesToRemove.push(node) is 1
+						watchersWithNodesToRemove.push watcher
 					n--

 			# find new nodes
@@ -478,27 +476,34 @@
 			pending = false
 			{watchers} = Watcher

-			tmp = currentWatchersSwitch
-			currentWatchersSwitch = nextWatchersSwitch
-			nextWatchersSwitch = tmp
-
+			# update watchers
 			for watcher in watchers
 				if watcher.node
 					updateWatcher watcher

+			# clear nodes
 			for watcher in watchers
 				if watcher.node
 					clearRec watcher.node
+
+			# emit signals
+			while watcher = watchersWithNodesToRemove.pop()
+				nodesToRemove = watcher._nodesToRemove
+				while node = nodesToRemove.pop()
+					emitSignal watcher, 'onRemove', node
+			while watcher = watchersWithNodesToAdd.pop()
+				nodesToAdd = watcher._nodesToAdd
+				while node = nodesToAdd.pop()
+					emitSignal watcher, 'onAdd', node
 			return

 		(node) ->
-			thisFlag = CHECK_WATCHERS_CHILDREN | currentWatchersSwitch
 			node._checkWatchers = CHECK_WATCHERS_THIS
 			tmp = node
 			while tmp
-				if tmp._checkWatchers & currentWatchersSwitch
+				if tmp._checkWatchers & CHECK_WATCHERS_CHILDREN
 					break
-				tmp._checkWatchers |= thisFlag
+				tmp._checkWatchers |= CHECK_WATCHERS_CHILDREN
 				tmp = tmp._parent

 			unless pending


Tests NOT - REMOVEPROP written

Tests NOT - REMOVEPROP written

--- test/lib/dom.coffee
+++ test/lib/dom.coffee
@@ -1,5 +1,5 @@

-casper.test.begin "DOM Testing", 29, (test) ->
+casper.test.begin "DOM Testing", 44, (test) ->

   casper.start()
@@ -437,6 +437,9 @@
     # The INDEX function returns the index of the current element in the set.
     # If you don't specify the selector it will return the first node.

+    # NOTE: Couldn't figure out how to get this to work. Seems like this
+    # should work.
+
     .then ->

       testThing = @.evaluate ->
@@ -543,6 +546,9 @@

     # The NEXT function returns an object with the set of siblings of each
     # element in the original set.
+
+    # NOTE: Maybe I don't know what I'm doing but I don't think this is
+    # working correctly.

     .then ->

@@ -553,7 +559,9 @@
         junction(".next").append("

")
         junction(".next").append("

")

-        thing = junction("body").find(".next")
+        thing = junction("body").find(".next > div")
+        thing.each ->
+          __utils__.echo this
         __utils__.echo thing.length
         __utils__.echo thing.next().length

@@ -569,22 +577,349 @@

     # NOT ---------------------------------------------------------------------
+
+    # The NOT function removes the selected elements from the current set
+
+    .then ->
+
+      isSoCount = @.evaluate ->
+
+        junction("body").append("

")
+        junction(".not").append("

")
+        junction(".not").append("

")
+        junction(".not").append("

")
+
+        thing = junction(".not > div")
+        return thing.not(".is-not").length
+
+      test.assertEquals isSoCount, 1, ["Not is successful."]
+
+      return
+
+
     # OFFSET ------------------------------------------------------------------
-    # OUTERWIDTH --------------------------------------------------------------
-    # ------------------ GET TO HERE ------------------------------------------
+
+    # The OFFSET function returns an object with the "top" and "left" properties
+    # of the first elements offsets
+
+    # SHOESTRING does NOT have a test for this.
+
+    .then ->
+
+      test.assertEquals 1, 1, ["Offset is not finished."]
+
+      return
+
+
     # PARENT ------------------------------------------------------------------
+
+    # The PARENT function returns the first parents for each element in the set.
+
+    # TODO: Add a second test for the second child
+
+    .then ->
+
+      parent = @.evaluate ->
+
+        junction("body").append("

")
+        junction(".parent").append("

")
+        junction(".parent").append("

")
+
+        return junction(".parent")[0]
+
+      childOneParent = @.evaluate ->
+
+        children = junction(".child")
+
+        return children.parent()[0]
+
+      test.assertEquals childOneParent.attributes[0].textContent,
+                        parent.attributes[0].textContent,
+                        ["Parent is successful."]
+
+      return
+
+
     # PARENTS -----------------------------------------------------------------
+
+    # The PARENTS functions returns the set of all parents matching the selector
+    # for each element in the set.
+
+    # TODO: create tests for the rest of the parents array. See "echo" for the
+    # rest of the items.
+
+    .then ->
+
+      @.evaluate ->
+
+        junction("body").append("

")
+        junction(".parents").append("

")
+        junction(".parentOne").append("

")
+        junction(".parents").append("

")
+        junction(".parentTwo").append("

")
+
+        parents = junction(".parents")
+
+        children = parents.find(".child")
+
+        # __utils__.echo children.parents().length
+        # __utils__.echo children.parents()[0].attributes[0].textContent
+        # __utils__.echo children.parents()[1].attributes[0].textContent
+        # __utils__.echo children.parents()[2]
+        # __utils__.echo children.parents()[3].innerHTML
+        # __utils__.echo junction("html")[0].innerHTML
+        # __utils__.echo children.parents()[4].attributes[0].textContent
+
+      index0 = @.evaluate ->
+
+        indexThing = junction(".parents").find(".child").parents()[0]
+        return indexThing.attributes[0].textContent
+
+      index1 = @.evaluate ->
+
+        indexThing = junction(".parents").find(".child").parents()[1]
+        return indexThing.attributes[0].textContent
+
+      test.assertEquals index0, "parentOne", ["Parents returns correct parent at index 0."]
+      test.assertEquals index1, "parents", ["Parents returns correct parent at index 1"]
+
+      return
+
+
     # PREPEND -----------------------------------------------------------------
+
+    # The PREPEND function adds the element or HTML string before the children
+    # of each element in the set.
+
+    # NOTE: It looks like this function doesn't work right now.
+
+    .then ->
+
+      @.evaluate ->
+
+        junction("body").append("

")
+
+        junction(".prepend").prepend("

")
+
+      test.assertEquals 1, 1, ["Prepend is not finished."]
+      # test.assertExists ".firstPrependedThing", ["Prepend is successful"]
+
+      # @.evaluate ->
+      #
+      #   junction("body").append "

"
+      #
+      # test.assertExists "#second", ["Append is successful"]
+      # @.evaluate ->
+      #
+      #   junction("

").appendTo("body")
+      #
+      # test.assertExists "#appendTo", ["AppendTo is successful"]
+      return
+
+
     # PREPENDTO ---------------------------------------------------------------
+
+    # The PREPENDTO function adds each element of the current set BEFORE the
+    # children of the selected elements.
+
+    # NOTE: I just going to guess that this function doesn't work yet. The
+    # Shoestring docs show the test for it using AppendTo. That tells me it
+    # doesn't work yet. Will try to clarify and then revisit.
+
     # PREV --------------------------------------------------------------------
+
+    # The PREV function returns an object containing one sibling before each
+    # element in the original set.
+
+    # It doesn't seem like this is working either. According to the Shoestring
+    # tests thing.prev().length here should return 1.
+
+    .then ->
+
+      @.evaluate ->
+
+        junction("body").append("

")
+        junction(".prev").append("

")
+        junction(".prev").append("

")
+        junction(".prev").append("

")
+
+        thing = junction(".prev div.prevThree")
+        __utils__.echo thing.attr("class")
+        __utils__.echo thing.prev().length
+
+      test.assertEquals 1, 1, ["Prev is not finished."]
+
+      return
+
+
     # PREVALL -----------------------------------------------------------------
+
+    # The PREVALL function returns an object with the set of ALL siblings before
+    # each element in the original set.
+
+    # This doesn't look like it's working. According to the Shoestring tests
+    # thing.prevAll().length should be returning 2.
+
+    .then ->
+
+      @.evaluate ->
+
+        junction("body").append("

")
+        junction(".prevall").append("

")
+        junction(".prevall").append("

")
+        junction(".prevall").append("

")
+
+        thing = junction(".prevall div.prevallThree")
+        __utils__.echo thing.prevAll().length
+
+      test.assertEquals 1, 1, ["Prevall is not finished."]
+
+      return
+
+
     # PROP --------------------------------------------------------------------
+
+    # The PROP function gets the property value on the first element or sets
+    # the property value on all the elements in the set.
+
+    .then ->
+
+      # gets the property
+      property = @.evaluate ->
+
+        junction("body").append("

")
+
+        return junction(".prop").prop("class")
+
+      test.assertEquals property, "prop", ["Prop gets the property correctly"]
+
+      property = @.evaluate ->
+
+        junction(".prop").prop("class", "bar")
+
+        return junction(".bar").prop("class")
+
+      test.assertEquals property, "bar", ["Prop sets the property correctly."]
+
+      return
+
+
     # PROPFIX -----------------------------------------------------------------
+
+    # SHOESTRING does not have a test for this.
+
     # REMOVE ------------------------------------------------------------------
+
+    # The REMOVE function removes the current set of elements from the DOM.
+
+    .then ->
+
+      @.evaluate ->
+
+        junction("body").append("

")
+
+        junction(".remove").remove()
+
+      test.assertDoesntExist ".remove", ["Remove is successful."]
+
+      return
+
+
     # REMOVEATTR --------------------------------------------------------------
+
+    # The REMOVEATTR function removes an attribute from each element in the
+    # current set
+
+    .then ->
+
+      before = @.evaluate ->
+
+        junction("body").append("

")
+        return junction(".removeattr")[0]
+
+      after = @.evaluate ->
+
+        junction(".removeattr").removeAttr("data-foo")
+        return junction(".removeattr")[0]
+
+      test.assertNotEquals after.attributes[1],
+                          before.attributes[1],
+                          ["RemoveAttr removed the attribute"]
+
+      return
+
+
     # REMOVECLASS -------------------------------------------------------------
+
+    # The REMOVECLASS function removes the class from the DOM for each element
+    # in the set.
+
+    .then ->
+
+      before = @.evaluate ->
+
+        junction("body").append("

")
+        return junction(".removeClass")[0]
+
+      after = @.evaluate ->
+
+        junction(".removeClass").removeClass("foo")
+        return junction(".removeClass")[0]
+
+      test.assertNotEquals after.className,
+                          before.className,
+                          ["RemoveClass removed the class."]
+
+      return
+
+
     # REMOVEPROP --------------------------------------------------------------
+
+    # The REMOVEPROP function removes a property from each element of the
+    # current set.
+
+    .then ->
+
+      before = @.evaluate ->
+
+        junction("body").append("

")
+        return junction(".removeProp")
+
+      after = @.evaluate ->
+
+        junction(".removeProp").removeProp("class")
+        return junction(".removeProp")
+
+      test.assertNotEquals after, before, ["RemoveProp removed the property."]
+
+      return
+
+
     # REPLACEWITH -------------------------------------------------------------
+
+    # The REPLACEWITH function replaces each element in the current set with
+    # the element or string specified.
+
+    .then ->
+
+      before = @.evaluate ->
+
+        junction("body").append("

")
+        return junction(".replaceWith")[0]
+
+      after = @.evaluate ->
+
+        junction(".replaceWith").replaceWith("

")
+        return junction(".replacement")[0]
+
+      @.echo before
+      @.echo after
+
+      test.assertEquals 1, 1, ["ReplaceWith is not finished."]
+
+      return
+
+
     # SERIALIZE ---------------------------------------------------------------
     # SHOW --------------------------------------------------------------------
     # SIBLINGS ----------------------------------------------------------------


updating task plan mixin to remove listener

--- src/components/task-plan/plan-mixin.coffee
+++ src/components/task-plan/plan-mixin.coffee
@@ -60,7 +60,7 @@

   saved: ->
     courseId = @props.courseId
-    TaskPlanStore.removeChangeListener(@saved)
+    TaskPlanStore.saved.removeListener(@saved)
     TaskPlanStore.isLoading(@props.id)
     @context.router.transitionTo('taskplans', {courseId})


test PATCH /v1/users/:id with unknown id

--- test/rest/v1/users.coffee
+++ test/rest/v1/users.coffee
@@ -273,6 +273,33 @@
         res.body.error_description.should.equal 'An access token is required'

+    describe 'with unknown user id', ->
+
+      before (done) ->
+        sinon.stub(AccessToken, 'verify').callsArgWith(2, null, {})
+        sinon.stub(User, 'patch').callsArgWith(2, null, null)
+        request
+          .patch('/v1/users/uuid')
+          .set('Authorization', 'Bearer valid.signed.token')
+          .end (error, response) ->
+            err = error
+            res = response
+            done()
+
+      after ->
+        AccessToken.verify.restore()
+        User.patch.restore()
+
+      it 'should respond 404', ->
+        res.statusCode.should.equal 404
+
+      it 'should respond with JSON', ->
+        res.headers['content-type'].should.contain 'text/html'
+
+      it 'should respond with "Not found."', ->
+        res.text.should.equal 'Not found.'
+
+
     describe 'with valid data', ->

       user = new User name: 'Jim'


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/metered.coffee
+++ test/metered.coffee
@@ -4,6 +4,6 @@
   obj = GameObject()
   obj.include(Metered)

-  ok obj.addMet
+  ok obj.addMeter

 module()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/metered.coffee
+++ test/metered.coffee
@@ -18,6 +18,6 @@

   equals obj.I.maxHealth, 150

-test "should set both <> and maxName", ->
+test "should set both <attribute> and maxName", ->

 module()


add padding on src code link

add padding on src code link

--- views/layout.coffee
+++ views/layout.coffee
@@ -20,5 +20,5 @@
         p ->
           em 'Powered with Meryl, Node.js, coffee-script, Redis nowJS and coffeeKup!'
           br
-          a href: 'http://github.com/gradus/node-chess', style: 'padding-left:50px', ->
+          a href: 'http://github.com/gradus/node-chess', style: 'padding:30px 20px', ->
             'Source Code'


render piece at negative values for 'a' parameter

--- views/puzzle.coffee
+++ views/puzzle.coffee
@@ -2,7 +2,7 @@

   constructor: () ->
     @puzzle_view = new PuzzleView(this)
-    @grid_model = new PuzzleGridModel
+    @grid_model = new PuzzleGridModel(this)
     @hex_grid = new HexGrid(this)
     @hex_draw = new HexDraw(this)

@@ -23,8 +23,8 @@
     pc = Math.floor(16*Math.random())
     @piece.construct_piece(pieces[pc])

-#    @pixel_test = new PixelHexTester(this)
-#    @pixel_test.test(100000)
+    @pixel_test = new PixelHexTester(this)
+    @pixel_test.adjusted_hex_quick_mark()

@@ -35,6 +35,8 @@
     @grid_model = @puzzle.grid_model

     @piece_drag = new PieceDrag(@puzzle)
+
+    @show_clicks = true

   click_handle: (e) ->
@@ -47,11 +49,10 @@
     y = py-dy

     console.log("mouse click: "+x+","+y)
+    @puzzle.pixel_test.big_dot(x,y) if @show_clicks == true
     hex = @piece_drag.get_piece_hex_position(x,y)
     console.log("A*,B* ~= "+hex[0]+","+hex[1])
-      # TODO We must change the hx[0]!=0 test to something
-      # else when we implement negative a,b values.
-    @puzzle.piece.draw_piece_ab(hex[0],hex[1]) if hex[0] != 0
+    @puzzle.piece.draw_piece_ab(hex[0],hex[1])

@@ -165,9 +166,8 @@
     @redraw.reset_size(wd_ht[0],wd_ht[1])

     @piece_mask.draw_piece_pattern()
-    @draw_piece(0,0)
     @cut_piece_from_photo()
-    @draw_piece(0,100)
+    @draw_piece_ab(-7,5)

   cut_piece_from_photo: () ->
@@ -183,9 +183,6 @@

     photo = document.getElementById(@puzzle.puzzle_view.photo)
     @photo_clip_context.drawImage(photo,xx,yy,@hex_box.width,@hex_box.height,0,0,@hex_box.width,@hex_box.height)
-
-    view_context = @puzzle.puzzle_view.context_canvas
-    view_context.drawImage(@photo_clip,0,190)

     context = @piece_mask.piece_mask_context
     context.globalCompositeOperation = 'source-atop'
@@ -300,8 +297,10 @@

     @puzzle_xy = [100,30]

+    @top_margin = [100,0,384,30]
+    @right_margin = [484,0,36,246]
     @bottom_margin = [100,246,420,34]
-    @right_margin = [484,0,36,246]
+    @left_margin = [0,0,100,280]

     @backgrounds = ["#cc9999","#adcc99","#99c2cc",
                     "#c299cc","#ccad99","#99cc99",
@@ -319,11 +318,15 @@

   clear_margins: () ->
+    tp = @top_margin
+    rt = @right_margin
     bt = @bottom_margin
-    rt = @right_margin
+    lf = @left_margin
     @context = @get_drawing_context("canvas")
+    @context.clearRect(tp[0],tp[1],tp[2],tp[3])
+    @context.clearRect(rt[0],rt[1],rt[2],rt[3])
     @context.clearRect(bt[0],bt[1],bt[2],bt[3])
-    @context.clearRect(rt[0],rt[1],rt[2],rt[3]) # TODO need to add top margin
+    @context.clearRect(lf[0],lf[1],lf[2],lf[3])

   get_drawing_context: (mode) ->
@@ -339,45 +342,32 @@

 class PuzzleGridModel

-    # TODO Since this is the only method left in this class,
-    #      what is the future of this class?
+  constructor: (puzzle_app) ->
+    @puzzle = puzzle_app
+
+
   get_hex: (x,y) ->
-    hex = [0,0]
+    hex = [99,99]
     in_bounds = true
     aa = Math.floor((x-12)/14)-7
-    bb = Math.floor((y-9*(aa%2)+2.5)/20)-1
-    in_bounds = false if @in_range(aa,bb) == false
-
-    corner = @get_xy(aa,bb)
+    if aa%2 != 0 then odd = 1 else odd = 0
+    bb = Math.floor((y-9*odd+2.5)/20)-1
+    console.log("ab = "+[aa,bb])
+    corner = @puzzle.hex_box.get_box_xy_ab(aa,bb)
+    console.log("corner = "+corner)
     ctr_x = corner[0]+9
     ctr_y = corner[1]+10
+    console.log("center = "+ctr_x+","+ctr_y)
     dx = Math.abs(x-ctr_x)
     dy = Math.abs(y-ctr_y)
+    console.log("dx = "+dx)
+    console.log("dy = "+dy)
     r2 = dx*dx+dy*dy
+    console.log("r2 = "+r2)
     in_bounds = false if r2>67 #(if radius > 8.2)

     hex = [aa,bb] if (in_bounds == true)
     return hex
-
-
-  get_xy: (a,b) ->
-    if @in_range(a,b)
-      x = 100 + 14*a + (a%2)/2 + @t_dx
-      y = 30 + 20*b + (a%2)*10 + @t_dy
-      xy = [x,y]
-    else
-      xy = [0,0]
-    return xy
-
-
-  in_range: (a,b) ->
-    a = 0 if not a?
-    b = 0 if not b?
-    ok = true
-    ok = false if a<1 || a>24
-    ok = false if b<1 || b>10
-    ok = false if b == 10 && a%2 == 1
-    return ok

@@ -403,12 +393,14 @@

   get_hex_xy: (a,b) ->
     x = a*14 + @dx
-    y = (2*b + a%2)*10 + @dy
+    if a%2 != 0 then odd = 1 else odd = 0
+    y = (2*b + odd)*10 + @dy
     xy = [x,y]
     return xy

   fill_hex_ab: (a,b,c_no) ->
+    alert("fill_hex_ab: "+a+","+b+"  "+c_no) if c_no == 1
     xy = @get_hex_xy(a,b)
     @fill_hex_xy(xy[0],xy[1],c_no)

@@ -589,7 +581,6 @@

   constructor: (puzzle_app) ->
     @puzzle = puzzle_app
-    @grid_model = @puzzle.grid_model
     @xx = 120
     @yy = 30
     @wd = 350
@@ -598,7 +589,9 @@

   test: (n) ->
     for k in [0..n]
-      @pixel_test()
+      for z in [1.1000]
+        @pixel_test()
+        alert("pixel test: "+k)

   pixel_test: () ->
@@ -607,11 +600,46 @@
     @dot(x,y)

+  hex_quick_mark: () ->
+    for aa in [1..24]
+      for bb in [1..10]
+        if not (aa%2 == 1 and bb == 10)
+          corner = @puzzle.hex_box.get_box_xy_ab(aa,bb)
+          ctr_x = corner[0]+9
+          ctr_y = corner[1]+10
+          @circle_hex(ctr_x,ctr_y)
+
+
+  adjusted_hex_quick_mark: () ->
+    dxy = @puzzle.events.piece_drag.get_piece_offset()
+    for aa in [-7..24]
+      for bb in [1..10]
+        if aa == 1 && bb == 1 then origin = true else origin = false
+        if not (aa%2 == 1 and bb == 10)
+          corner = @puzzle.hex_box.get_box_xy_ab(aa,bb)
+          ctr_x = corner[0]+9+dxy[0]
+          ctr_y = corner[1]+10+dxy[1]
+          hx11 = [corner[0]-9,corner[1]-10] if aa == 1 && bb == 1
+          @circle_hex(ctr_x,ctr_y,origin)
+
+
+  circle_hex: (x,y,origin) ->
+    canvas = document.getElementById("puzzle-widget")
+    context = canvas.getContext('2d')
+    context.strokeStyle = "black"
+    context.fillStyle = "blue"
+    context.beginPath()
+    context.arc(x,y,8.2,0,6.28)
+    context.stroke()
+    context.fill() if origin == true
+    context.closePath()
+
+
   mark_hex_centerpoints: () ->
     for aa in [1..24]
       for bb in [1..10]
         if not (aa%2 == 1 and bb == 10)
-          corner = @grid_model.get_xy(aa,bb)
+          corner = @puzzle.hex_box.get_box_xy_ab(aa,bb)
           @put_dot(corner[0],corner[1],"#666666")
           ctr_x = corner[0]+9
           ctr_y = corner[1]+10
@@ -620,7 +648,6 @@

   dot: (x,y) ->
     hex = @puzzle.events.piece_drag.get_piece_hex_position(x,y)
-#    hex = @grid_model.get_hex(x,y)
     if hex[0] > 0 and hex[0] < 25 and hex[1] > 0 and hex[1] < 11
       color = @get_dot_color(hex)
       @put_dot(x,y,color)
@@ -631,6 +658,13 @@
     context = canvas.getContext('2d')
     context.fillStyle = color
     context.fillRect(x,y,1,1)
+
+
+  big_dot: (x,y) ->
+    canvas = document.getElementById("puzzle-widget")
+    context = canvas.getContext('2d')
+    context.fillStyle = "black"
+    context.fillRect(x-1,y-1,3,3)

   color_name: (cc) ->


Fix typo: seperated -> separated

Fix typo: seperated -> separated

--- test/spec/YamlSpec.coffee
+++ test/spec/YamlSpec.coffee
@@ -423,11 +423,11 @@

         expect withDatesToTime(YAML.parse """
             iso8601: 2001-12-14t21:59:43.010+05:00
-            space seperated: 2001-12-14 21:59:43.010 -05:00
+            space separated: 2001-12-14 21:59:43.010 -05:00
         """)
         .toEqual withDatesToTime (
             'iso8601': iso8601Date
-            'space seperated': spaceSeparatedDate
+            'space separated': spaceSeparatedDate
         )

@@ -1161,11 +1161,11 @@

         expect withDatesToTime(YAML.parse """
             iso8601: 2001-12-14t21:59:43.010-05:00
-            space seperated: 2001-12-14 21:59:43.010 +05:00
+            space separated: 2001-12-14 21:59:43.010 +05:00
         """)
         .toEqual YAML.parse YAML.dump withDatesToTime (
             'iso8601': iso8601Date
-            'space seperated': spaceSeparatedDate
+            'space separated': spaceSeparatedDate
         )


use readFileSync instead of require to circumvent cache issues

--- src/steroids/ApplicationConfigUpdater.coffee
+++ src/steroids/ApplicationConfigUpdater.coffee
@@ -124,6 +124,8 @@
         @updateTo3_1_0()
       ).then( =>
         @updateTo3_1_4()
+      ).then( =>
+        @ensurePackageJsonExists()
       ).then( =>
         @ensureNoBadGruntDeps()
       ).then( =>
@@ -464,13 +466,13 @@

     return deferred.promise

-
   addGruntSteroidsDependency: ->
     deferred = Q.defer()

     console.log("Adding #{chalk.bold("grunt-steroids")} devDependency to #{chalk.bold("package.json")}...")

-    packageJson = require(paths.application.configs.packageJson)
+    packageJsonData = fs.readFileSync paths.application.configs.packageJson, 'utf-8'
+    packageJson = JSON.parse(packageJsonData)

     if packageJson.devDependencies?
       packageJson.devDependencies["grunt-steroids"] = "0.x"
@@ -492,7 +494,8 @@

     console.log "Checking for erroneous npm dependencies..."

-    packageJson = require(paths.application.configs.packageJson)
+    packageJsonData = fs.readFileSync paths.application.configs.packageJson, 'utf-8'
+    packageJson = JSON.parse(packageJsonData)

     if (packageJson?.devDependencies?["grunt-extend-config"]? ||
        packageJson?.devDependencies?["grunt-contrib-clean"]? ||


Prevent the need to double-touch on iOS

Prevent the need to double-touch on iOS

Adds a "touchend" event along with the "click" which bypasses the iOS
issue with clicking on elements which have a hover state:

  http://stackoverflow.com/a/3426201

Closes #1450.

--- app/assets/javascripts/lib/models/chart_list.coffee
+++ app/assets/javascripts/lib/models/chart_list.coffee
@@ -218,7 +218,7 @@

     # Launch the chart picker popup
     #
-    $(document).on "click", "a.select_chart, a.add_chart", (e) ->
+    $(document).on "click touchend", "a.select_chart, a.add_chart", (e) ->
       e.preventDefault()
       url = $(this).attr('href')
       $.fancybox.open
@@ -232,7 +232,7 @@
           # Pick a chart from the chart picker popup
           #
           # $('body').on "click touchdown", "div.pick_chart", (e) =>
-          $('#select_charts .pick_chart').on 'click', (e) =>
+          $('#select_charts .pick_chart').on 'click touchend', (e) =>
             data_holder = $(e.target).closest('div.pick_chart')
             holder_id = data_holder.data('chart_holder')
             chart_id  = data_holder.data('chart_id')
@@ -243,7 +243,7 @@

     # Toggle chart lock
     #
-    $(document).on 'click', "a.lock_chart", (e) =>
+    $(document).on 'click touchend', "a.lock_chart", (e) =>
       e.preventDefault()
       # which chart are we talking about?
       holder_id = $(e.target).parents(".chart_holder").data('holder_id')
@@ -252,7 +252,7 @@

     # Remove chart
     #
-    $(document).on 'click', 'a.remove_chart', (e) =>
+    $(document).on 'click touchend', 'a.remove_chart', (e) =>
       e.preventDefault()
       holder_id = $(e.target).parents(".chart_holder").data('holder_id')
       $(".chart_holder[data-holder_id=#{holder_id}]").remove()
@@ -262,20 +262,20 @@

     # Toggle chart/table format
     #
-    $(document).on 'click', 'a.table_format, a.chart_format', (e) =>
+    $(document).on 'click touchend', 'a.table_format, a.chart_format', (e) =>
       e.preventDefault()
       holder_id = $(e.target).parents(".chart_holder").data('holder_id')
       @chart_in_holder(holder_id).toggle_format()

     # Restore the default chart
     #
-    $(document).on 'click', 'a.default_chart', (e) =>
+    $(document).on 'click touchend', 'a.default_chart', (e) =>
       e.preventDefault()
       @load_default()

     # Zoom chart
     #
-    $(document).on 'click', 'a.zoom_chart', (e) =>
+    $(document).on 'click touchend', 'a.zoom_chart', (e) =>
       e.preventDefault()
       holder_id = $(e.target).parents(".chart_holder").data('holder_id')
       chart = @chart_in_holder holder_id


OX-628 increased node selection threshold for 3d-view

--- app/assets/javascripts/oxalis/model/flycam2d.coffee
+++ app/assets/javascripts/oxalis/model/flycam2d.coffee
@@ -181,7 +181,7 @@

   setRayThreshold : (cameraRight, cameraLeft) ->
     # in nm
-    @rayThreshold[constants.VIEW_3D] = 4 * (cameraRight - cameraLeft) / 384
+    @rayThreshold[constants.VIEW_3D] = 8 * (cameraRight - cameraLeft) / 384

   getRayThreshold : (planeID) ->
     if planeID < 3


Fix error gestures in form elements

Fix error gestures in form elements

--- source/quo.gestures.coffee
+++ source/quo.gestures.coffee
@@ -15,6 +15,7 @@
   _handlers       = {}
   _data           = null
   _originalEvent  = null
+  _disabled_tags  = ["input", "select", "textarea"]

   $$(document).ready ->
     environment = $$ document.body
@@ -32,6 +33,7 @@

   # Private methods
   _start = (ev) ->
+    return if ev.srcElement.tagName.toLowerCase() in _disabled_tags
     _started = true
     _originalEvent = ev or event
     _data = _getFingersData(ev)


App.index: fix global variable for correct lock

--- client/app/lib/index.coffee
+++ client/app/lib/index.coffee
@@ -14,10 +14,9 @@
 ConnectionChecker = require './connectionchecker'
 lazyrouter = require './lazyrouter'

+isStarted = false

 module.exports = (defaults) ->
-
-  isStarted = false

   if isStarted then throw 'already running' else isStarted = true


Don't store dynamically generated fields into the database.

--- client/lib/annotator.coffee
+++ client/lib/annotator.coffee
@@ -232,7 +232,12 @@
     annotation.publication =
       _id: Session.get 'currentPublicationId'

-    Highlights.insert _.pick(annotation, '_id', 'author', 'publication', 'quote', 'target'), (error, id) =>
+    # Remove fields we do not want to store into the database
+    highlight = _.pick annotation, '_id', 'author', 'publication', 'quote', 'target'
+    highlight.target = _.map highlight.target, (t) =>
+      _.pick t, 'source', 'selector'
+
+    Highlights.insert highlight, (error, id) =>
       # Meteor triggers removal if insertion was unsuccessful, so we do not have to do anything
       throw error if error


Small optimization

Small optimization

--- framer/EventEmitter.coffee
+++ framer/EventEmitter.coffee
@@ -83,10 +83,12 @@
 		return @ unless @[EventKey][eventName]
 		for listener in @[EventKey][eventName]
 			@removeListener(eventName, listener)
+		return @

 	removeAllListeners: ->
 		return @ unless @[EventKey]
 		for eventName, eventListeners of @[EventKey]
 			for listener in eventListeners
 				@removeListener(eventName, listener)
+		return @


Offsetting translation with the pixel multiplier

--- framer/SVGBaseLayer.coffee
+++ framer/SVGBaseLayer.coffee
@@ -89,13 +89,14 @@
 			options.y ?= 0
 			options.rotation ?= 0
 			indicesToRemove = []
+			pixelMultiplier = Framer?.CurrentContext.pixelMultiplier ? 1
 			for i in [0...@_element.transform.baseVal.numberOfItems]
 				transform = @_element.transform.baseVal.getItem(i)
 				matrix = transform.matrix
 				switch transform.type
 					when 2 #SVG_TRANSFORM_TRANSLATE
-						options.x += matrix.e
-						options.y += matrix.f
+						options.x += matrix.e / pixelMultiplier
+						options.y += matrix.f / pixelMultiplier
 						indicesToRemove.push(i)
 					when 4 #SVG_TRANSFORM_ROTATE
 						# We willingly ignore the translation from this matrix


refs #32 tests update

refs #32 tests update

--- test/test-longjohn.coffee
+++ test/test-longjohn.coffee
@@ -80,6 +80,21 @@
     emitter.emit('foo')

     assert.equal(count, 1)
+
+  it 'should work with removeAllListeners (issue #32)', ->
+    {EventEmitter} = require 'events'
+
+    count = 0
+    foo = -> ++count
+
+    emitter = new EventEmitter()
+
+    emitter.on('removeListener', foo)
+    emitter.on('dummy', foo)
+
+    emitter.removeAllListeners('dummy', foo)
+
+    assert.equal(count, 1)

   it 'should work with setTimeout', (done) ->
     setTimeout ->


remove useless debugger

remove useless debugger

Change-Id: Icc6bbd6ad446328b39858e7a2ce953950cd2e9cb
Reviewed-on: https://gerrit.instructure.com/208762
Reviewed-by: Ryan Shaw <ea3cd978650417470535f3a4725b6b5042a6ab59@instructure.com>
Tested-by: Jenkins
QA-Review: Steven Burnett <a0b450117c5d20a22d5c4d52bb8ae13adee003d8@instructure.com>
Product-Review: Steven Burnett <a0b450117c5d20a22d5c4d52bb8ae13adee003d8@instructure.com>

--- app/coffeescripts/views/assignments/EditView.coffee
+++ app/coffeescripts/views/assignments/EditView.coffee
@@ -441,7 +441,6 @@
     #
     # If the user has not changed the due date, don't touch the seconds value
     # (so that we don't clobber a due date set by the API).
-    # debugger
     return null unless newDate

     adjustedDate = new Date(newDate)


adding herecomment in class definition test.

--- test/test_comments.coffee
+++ test/test_comments.coffee
@@ -144,3 +144,14 @@
   ###

 fn2 = ->
+
+
+class A
+  b: ->
+
+  ###
+  Comment
+  ###
+  c: ->
+
+ok A.prototype.c instanceof Function


More precise shooting (any offset rather than 8 dirs)

--- src/definitions/items.coffee
+++ src/definitions/items.coffee
@@ -28,39 +28,48 @@
 			else '_dud'

 	fireHandlers:
-		'_dud': (creature, dir) ->
+		'_dud': (creature, offset) ->
 			game.message 'Nothing happens; this gun is a dud.'

-		'line': (creature, dir) ->
-			game.emit 'game.creature.fire', creature, @, dir
+		'line': (creature, offset) ->
+			game.emit 'game.creature.fire', creature, @, offset

-			offset = direction.parse dir
-			endPos =
-				vectorMath.add creature, (
-					vectorMath.mult offset, @range
-				)
+			if _.isString offset
+				offset = direction.parse offset
+
+			{x, y} = offset
+			angle = Math.atan2 -y, x
+			offset =
+				x: Math.round Math.cos(angle) * @range
+				y: -Math.round Math.sin(angle) * @range
+
+			endPos = vectorMath.add creature, offset

 			found = creature.raytraceUntilBlocked endPos

 			switch found.type
 				when 'none'
-					game.emit 'game.creature.fire.hit.none', creature, @, dir
+					game.emit 'game.creature.fire.hit.none', creature, @, offset

 				when 'wall'
 					game.emit 'game.creature.fire.hit.wall',
-						creature, @, dir, found
+						creature, @, offset, found

 				when 'creature'
 					target = found.creature

-					game.emit 'game.creature.fire.hit.creature', creature, @, dir, target
+					game.emit 'game.creature.fire.hit.creature', creature, @, offset, target
 					target.damage 10, creature

-		'spread': (creature, dir) ->
-			game.emit 'game.creature.fire', creature, @, dir
+		'spread': (creature, offset) ->
+			game.emit 'game.creature.fire', creature, @, offset

-			# shotguns shoot in a spread - need angle of dir first
-			angle = direction.directionToRad dir
+			if _.isString offset
+				offset = direction.parse offset
+
+			# shotguns shoot in a spread - need angle of offset first
+			angle = Math.atan2 -offset.y, offset.x
+
 			compareAngles = (a0, a1) ->
 				Math.PI - Math.abs(Math.abs(a0-a1) - Math.PI)

@@ -80,8 +89,8 @@
 			if targets.length > 0
 				for target in targets
 					game.emit 'game.creature.fire.hit.creature',
-						creature, @, dir, target
+						creature, @, offset, target
 					target.damage 10, creature

 			else
-				game.emit 'game.creature.fire.hit.none', creature, @, dir+				game.emit 'game.creature.fire.hit.none', creature, @, offset


add behavior for link removing el from dom & db

using just markup you can handle removing an element
from the dom and the db

Change-Id: I56965764a9b695a206ae5233fb7ea3199607c2ef
Reviewed-on: https://gerrit.instructure.com/10027
Tested-by: Hudson <dfb64870173313a9a7b56f814c8e3b33e268497a@instructure.com>
Reviewed-by: Ryan Florence <2d8900489f91ac272fd9ce9d5789db6522668ca2@instructure.com>

--- app/coffeescripts/behaviors/ujsLinks.coffee
+++ app/coffeescripts/behaviors/ujsLinks.coffee
@@ -29,13 +29,50 @@

     confirm(message)

-  $(document).delegate 'a[data-confirm], a[data-method]', 'click', (event) ->
+  $(document).delegate 'a[data-confirm], a[data-method], a[data-remove]', 'click', (event) ->
     $link = $(this)

-    return false if $link.data 'handled'
-    return false unless allowAction($link)
+    return false if $link.data('handled') || !allowAction($link)

-    if $link.data('method')
+    if $link.data('remove')
+      handleRemove($link)
+      return false
+
+    else if $link.data('method')
       handleMethod($link)
       return false

+  ##
+  # for clicking link to remove element from page and send DELETE request to remove it from db
+  # sample markup:
+  #

+  #   Clicking the × will make the .user div go away, if the ajax request fails it will reappear.
+  #    ×
+  #

+  handleRemove = ($link) ->
+    selector = $link.data('remove')
+    $startLookingFrom = $link
+    url = $link.data('url')
+
+    # special case for handling links inside of a KyleMenu that were appendedTo the body and are
+    # no longer children of where they should be
+    closestKyleMenu = $link.closest(':ui-popup').popup('option', 'trigger').data('KyleMenu')
+    if closestKyleMenu && closestKyleMenu.opts.appendMenuTo
+      $startLookingFrom = closestKyleMenu.$placeholder
+
+    $elToRemove = $startLookingFrom.closest(selector)
+
+    # bind the 'beforeremove' and 'remove' events if you want to handle this with your own code
+    # if you stop propigation this will not remove it
+    $elToRemove.bind
+      beforeremove: $.proxy($elToRemove, 'hide')
+      remove: $.proxy($elToRemove, 'remove')
+
+    $elToRemove.trigger 'beforeremove'
+    triggerRemove = -> $elToRemove.trigger 'remove'
+
+    if url
+      $.ajaxJSON url, "DELETE", triggerRemove, $.proxy($elToRemove, 'show')
+    else
+      triggerRemove()
+


terminal colors: added one more theme so that I have something to work with

--- salvus/page/console.coffee
+++ salvus/page/console.coffee
@@ -74,7 +74,7 @@
             highlight_mode : 'none'
             renderer    : 'auto'   # options -- 'auto' (best for device); 'codemirror' (mobile support), 'ttyjs' (xterm-color!)
             draggable   : false
-            colors      : 'solarized-dark'
+            colors      : 'low-contrast'

         @_init_default_settings()

@@ -185,26 +185,40 @@

     _init_colors: () =>
         # A list of 16 html color strings (br=bright)
-        #      [black,red,green,yellow,blue,magenta,cyan,white,brblack,brred,brgreen,bryellow,brblue,brmagenta,brcyan,brwhite]
+        #      [black,red,green,yellow,blue,magenta,cyan,white,
+        #       brblack,brred,brgreen,bryellow,brblue,brmagenta,brcyan,brwhite,
+        #       foreground,background]
         switch @opts.colors
             when 'default'   # black, red, green, yellow, blue, magenta, cyan, white
                 colors = [ '#000000', '#cc0000', '#4e9a06', '#c4a000', '#3465a4', '#75507b', '#06989a', '#d3d7cf',  # dark
-                           '#555753', '#ef2929', '#8ae234', '#fce94f', '#729fcf', '#ad7fa8', '#34e2e2', '#ffffff']  # bright
+                           '#555753', '#ef2929', '#8ae234', '#fce94f', '#729fcf', '#ad7fa8', '#34e2e2', '#ffffff', # bright
+                           '#0000000', '#ffffff']
             when 'solarized-light'  # see http://ethanschoonover.com/solarized; provides *two* color schemes
                 colors = [ '#073642', '#dc322f', '#859900', '#b58900', '#268bd2', '#d33682', '#2aa198', '#eee8d5',
-                           '#002b36', '#cb4b16', '#586e75', '#657b83', '#839496', '#6c71c4', '#93a1a1', '#fdf6e3']
+                           '#002b36', '#cb4b16', '#586e75', '#657b83', '#839496', '#6c71c4', '#93a1a1', '#fdf6e3',
+                           '#073642', '#fdf6e3']
             when 'solarized-dark'
                 colors = [ '#eee8d5', '#dc322f', '#859900', '#b58900', '#268bd2', '#d33682', '#2aa198', '#073642',
-                           '#fdf6e3', '#cb4b16', '#93a1a1', '#839496', '#657b83', '#6c71c4', '#586e75', '#002b36']
+                           '#fdf6e3', '#cb4b16', '#93a1a1', '#839496', '#657b83', '#6c71c4', '#586e75', '#002b36',
+                           '#eee8d5', '#002b36']
+            when 'low-contrast'
+                colors = [ '#222222', '#9E5641', '#6C7E55', '#CAAF2B', '#7FB8D8', '#956D9D', '#4c8ea1', '#808080',
+                           '#454545', '#CC896D', '#C4DF90', '#FFE080', '#B8DDEA', '#C18FCB', '#6bc1d0', '#cdcdcd',
+                           '#cdcdcd', '#343434']
             else
                 return

         for i in [0...16]
             Terminal.colors[i] = colors[i]

-        Terminal.defaultColors =
-            bg: Terminal.colors[15],
-            fg: Terminal.colors[0]
+        if colors.length > 16
+            Terminal.defaultColors =
+                fg: colors[16]
+                bg: colors[17]
+        else
+            Terminal.defaultColors =
+                fg: colors[15]
+                bg: colors[0]

         Terminal.colors[256] = Terminal.defaultColors.bg
         Terminal.colors[257] = Terminal.defaultColors.fg


removed extra create project command in generator tests

--- spec/Generator.spec.coffee
+++ spec/Generator.spec.coffee
@@ -39,9 +39,7 @@
       runs ->
         expect( cmd.stdout ).toMatch(/Error: No such tutorial neverGonnaExistTutorial, see 'steroids generate' for help./)

-
     it "complains about overwriting the application.coffee", ->
-      @testHelper.createProjectSync()

       cmd = @testHelper.runInProjectSync "generate",
         args: ["tutorial", "steroids"]
@@ -51,7 +49,6 @@
         expect( cmd.stdout).toMatch(/would be overwritten by this command/)

     it "generates begin tutorial", ->
-      @testHelper.createProjectSync()

       cmd = @testHelper.runInProjectSync "generate",
         args: ["tutorial", "begin"]
@@ -59,14 +56,13 @@
       runs ->
         expect( cmd.code ).toBe(0)
         expect( cmd.stdout ).toMatch(/steroids.config.location = "tutorial.html"/)
-
+
         tutorialFileLocation = path.join(@testHelper.testAppPath, "www", "tutorial.html")
         expect( fs.existsSync tutorialFileLocation ).toBe true
-
+
         expect( fs.readFileSync( tutorialFileLocation ).toString() ).toMatch(/Awesome, welcome!/)

     it "generates steroids tutorial", ->
-      @testHelper.createProjectSync()

       runs ->
         fs.unlinkSync( path.join(@testHelper.testAppPath, "config", "application.coffee") )
@@ -83,9 +79,7 @@

         expect( fs.readFileSync(tutorialStartHTMLPath ).toString()).toMatch(/Hooray!/)

-
     it "generates controllers tutorial after steroids", ->
-      @testHelper.createProjectSync()

       runs ->
         fs.unlinkSync( path.join(@testHelper.testAppPath, "config", "application.coffee") )
@@ -350,4 +344,3 @@

           expect(@cmd2.stdout).toMatch(/would be overwritten by this command/)

-


added createCSV function

added createCSV function

--- src/driver/driverUtil.coffee
+++ src/driver/driverUtil.coffee
@@ -76,6 +76,32 @@

   return

+collectSplits = (node) ->
+  if node.splits?
+    return Array::concat.apply([], node.splits.map(collectSplits))
+  else
+    return node.prop
+
+stripProp = (splits) ->
+  columns = _.unique(Array::concat.apply([], splits.map((split) -> return _.keys(split))))
+  data = splits.map((split) -> return columns.map((column) -> split[column] or null))
+  return {columns, data}
+
+exports.createMatrix = (node) ->
+  return stripProp(collectSplits(node))
+
+exports.createCSVFromMatrix = ({columns, data}) ->
+  header = columns.map((column) -> return '\"' + column + '\"').join(',')
+  content = data.map((row) ->
+    return row.map((datum) ->
+      if datum?
+        return '\"' + datum + '\"'
+      else
+        return '\"0\"'
+    ).join(',')
+  ).join('\r\n')
+  return header + '\r\n' + content
+
 # -----------------------------------------------------
 # Handle commonJS crap
 if typeof module is 'undefined' then window['driverUtil'] = exports else module.exports = exports


Tests for Put operator replacing current selection

--- spec/operators-spec.coffee
+++ spec/operators-spec.coffee
@@ -541,6 +541,14 @@

           expect(editor.getText()).toBe "abcdetwo three\none "

+      describe "with a selection", ->
+        beforeEach ->
+          editor.selectRight()
+          keydown('p')
+
+        it "replaces the current selection", ->
+          expect(editor.getText()).toBe "34512\n"
+          expect(editor.getCursorScreenPosition()).toEqual [0, 2]

     describe "with linewise contents", ->
       beforeEach ->
@@ -552,6 +560,18 @@
       it "inserts the contents of the default register", ->
         expect(editor.getText()).toBe "012\n 345"
         expect(editor.getCursorScreenPosition()).toEqual [1, 1]
+
+    describe "with linewise contents", ->
+      beforeEach ->
+        editor.getBuffer().setText("012")
+        editor.setCursorScreenPosition([0, 1])
+        editor.selectRight()
+        vimState.setRegister('"', text: " 345\n", type: 'linewise')
+        keydown('p')
+
+      it "replaces the current selection", ->
+        expect(editor.getText()).toBe "0 345\n2"
+        expect(editor.getCursorScreenPosition()).toEqual [0, 2]

     describe "with linewise contents", ->
       beforeEach ->


more tests...

more tests...

--- spec/specs/awaiting.coffee
+++ spec/specs/awaiting.coffee
@@ -39,6 +39,16 @@
         p = Bacon.constant(1)
         p.awaiting(p.map())
       [false])
+    expectPropertyEvents(
+      ->
+        p = Bacon.constant(1)
+        p.map().awaiting(p.map())
+      [false])
+    expectPropertyEvents(
+      ->
+        p = Bacon.constant(1)
+        p.map().awaiting(p)
+      [false])
   describe "works for awaiting self.flatMap", ->
     expectPropertyEvents(
       ->


Run symlink test only on Darwin

Run symlink test only on Darwin

--- spec/tree-view-spec.coffee
+++ spec/tree-view-spec.coffee
@@ -2717,7 +2717,7 @@
           atom.commands.dispatch(treeView.element, 'tree-view:remove')
           expect(dirView.directory.updateStatus).toHaveBeenCalled()

-    describe "when the project is a symbolic link to the repository root", ->
+    describe "on #darwin, when the project is a symbolic link to the repository root", ->
       beforeEach ->
         symlinkPath = temp.path('tree-view-project')
         fs.symlinkSync(projectPath, symlinkPath, 'junction')
@@ -2739,12 +2739,8 @@
           fs.writeFileSync(modifiedFile, originalFileContent)
           atom.project.getRepositories()[0].getPathStatus(modifiedFile)

-          waitsFor ->
-            not treeView.element.querySelector('.file.status-modified')
-
-          runs ->
-            expect(treeView.element.querySelector('.file.status-modified')).not.toExist()
-            expect(treeView.element.querySelector('.directory.status-modified')).not.toExist()
+          expect(treeView.element.querySelector('.file.status-modified')).not.toExist()
+          expect(treeView.element.querySelector('.directory.status-modified')).not.toExist()

   describe "when the resize handle is double clicked", ->
     beforeEach ->


Firewall: Added static fetch method to JProxyRestriction.

--- workers/social/lib/social/models/proxy/restriction.coffee
+++ workers/social/lib/social/models/proxy/restriction.coffee
@@ -29,6 +29,8 @@
         create    :
           (signature Object, Function)
         remove    :
+          (signature Object, Function)
+        fetch     :
           (signature Object, Function)
       instance    : {}

@@ -84,3 +86,10 @@
     validate client, data, (err, restriction) ->
       restriction.update { $pullAll: { filters: [filterId] } }, (err) ->
         callback err, restriction
+
+  @fetch: secure (client, query, callback) ->
+    query.owner = client.connection.delegate.getId()
+
+    JProxyRestriction.some query, {}, (err, restrictions) ->
+      return callback err, null  if err
+      callback null, restrictions


Social: add endpoints into node-bongo

Social: add endpoints into node-bongo

--- workers/social/lib/social/models/socialapi/channel.coffee
+++ workers/social/lib/social/models/socialapi/channel.coffee
@@ -19,6 +19,8 @@
         fetchParticipants :
           (signature Object, Function)
         fetchPopularTopics:
+          (signature Object, Function)
+        fetchPopularPosts:
           (signature Object, Function)
         fetchPinnedMessages:
           (signature Object, Function)
@@ -110,6 +112,9 @@
   @fetchPopularTopics = secure (client, options = {}, callback)->
     @doRequest 'fetchPopularTopics', client, options, callback

+  @fetchPopularPosts = secure (client, options = {}, callback)->
+    @doRequest 'fetchPopularPosts', client, options, callback
+
   @fetchChannels = secure (client, options = {}, callback)->
     @doRequest 'fetchGroupChannels', client, options, callback


hub/verify email: keep existing token

hub/verify email: keep existing token

--- src/smc-hub/postgres-server-queries.coffee
+++ src/smc-hub/postgres-server-queries.coffee
@@ -713,16 +713,24 @@
         locals =
             email_address : undefined
             token         : undefined
+            old_challenge : undefined

         async.series([
             (cb) =>
                 @_query
-                    query : "SELECT email_address FROM accounts"
+                    query : "SELECT email_address, email_address_challenge FROM accounts"
                     where : "account_id = $::UUID" : opts.account_id
-                    cb    : one_result 'email_address', (err, x) =>
-                        locals.email_address = x
+                    cb    : one_result (err, x) =>
+                        locals.email_address = x?.email_address
+                        locals.old_challenge = x?.email_address_challenge
                         cb(err)
             (cb) =>
+                # TODO maybe expire tokens after some time
+                if locals.old_challenge?.token?
+                    locals.token = locals.old_challenge.token
+                    cb()
+                    return
+
                 {generate} = require("random-key")
                 locals.token = generate(16).toLowerCase()
                 data =


Document skipping placeholder

Document skipping placeholder

--- src/trix/controllers/input_controller.coffee
+++ src/trix/controllers/input_controller.coffee
@@ -229,6 +229,7 @@

     compositionstart: (event) ->
       unless @selectionIsExpanded()
+        # Skip placeholder if keypress input was received before the composition started
         unless @inputSummary.eventName is "keypress" and @inputSummary.textAdded
           textAdded = @responder?.insertPlaceholder()
           @setInputSummary({textAdded})


Improve detecting @element in the DOM

Improve detecting @element in the DOM

--- src/trix/observers/selection_observer.coffee
+++ src/trix/observers/selection_observer.coffee
@@ -14,14 +14,14 @@
     delete @running

   tick: =>
-    if elementWasRemovedFromDOM(@element)
-      @stop()
-    else
+    if document.contains(@element)
       range = getRange()
       unless rangesAreEqual(range, @range)
         @delegate?.selectionDidChange?()
         @range = range
       requestAnimationFrame(@tick)
+    else
+      @stop()

   getRange = ->
     selection = window.getSelection()
@@ -32,6 +32,3 @@
       left?.startOffset is right?.startOffset and
       left?.endContainer is right?.endContainer and
       left?.endOffset is right?.endOffset
-
-  elementWasRemovedFromDOM = (element) ->
-    not element.parentNode


Fix arrayToSentence with a single element array

If the array had only one one element it'd still have a useless & prepended

--- src/flux/student-dashboard.coffee
+++ src/flux/student-dashboard.coffee
@@ -9,7 +9,10 @@
     week.push(event)

 arrayToSentence = (arry) ->
-  arry.slice(0, arry.length - 1).join(', ') + " & " + arry.slice(-1)
+  if arry.length > 1
+    arry.slice(0, arry.length - 1).join(', ') + " & " + arry.slice(-1)
+  else
+    arry[0]

 StudentDashboardConfig = {


fix argument checking in generate_time_string

--- scripts/chat-logger.coffee
+++ scripts/chat-logger.coffee
@@ -135,7 +135,7 @@
     [] # TODO

   generate_time_string = (d) ->
-    d = new Date d if d instanceof Number
+    d = new Date d unless d instanceof Date
     sprintf '%02d:%02d.%04d', d.getUTCMinutes(), d.getUTCSeconds(), d.getUTCMilliseconds()

   render_item = (item) ->


Fix broken renderer methods (why this isn't a problem is unclear)

--- bokehjs/src/coffee/models/renderers/renderer.coffee
+++ bokehjs/src/coffee/models/renderers/renderer.coffee
@@ -226,7 +226,7 @@
       if prop instanceof p.Distance
         @["max_#{name}"] = array_max(@[name])

-    if @renderer.plot_model.use_map
+    if @plot_model.use_map
       if @x?
         [@x, @y] = @project_xy(@x, @y)
       if @xs?
@@ -249,6 +249,7 @@

   _set_data: () -> null
   _map_data: () -> null
+  _index_data: () -> null
   _mask_data: (inds) -> inds
   _bounds: (bds) -> bds


Transpile packages with pack-specific transpiler during tests

--- src/initialize-test-window.coffee
+++ src/initialize-test-window.coffee
@@ -19,6 +19,7 @@
     path = require 'path'
     {ipcRenderer} = require 'electron'
     {getWindowLoadSettings} = require './window-load-settings-helpers'
+    CompileCache = require './compile-cache'
     AtomEnvironment = require '../src/atom-environment'
     ApplicationDelegate = require '../src/application-delegate'
     Clipboard = require '../src/clipboard'
@@ -58,6 +59,13 @@
     require('module').globalPaths.push(exportsPath)
     process.env.NODE_PATH = exportsPath # Set NODE_PATH env variable since tasks may need it.

+    # Set up optional transpilation for packages under test if any
+    FindParentDir = require 'find-parent-dir'
+    if packageRoot = FindParentDir.sync(testPaths[0], 'package.json')
+      packageMetadata = require(path.join(packageRoot, 'package.json'))
+      if packageMetadata.atomTranspilers
+        CompileCache.addTranspilerConfigForPath(packageRoot, packageMetadata.name, packageMetadata, packageMetadata.atomTranspilers)
+
     document.title = "Spec Suite"

     clipboard = new Clipboard


ComputeProvider: Digitalocean: remove credential check which is handling in kloud kite

--- workers/social/lib/social/models/computeproviders/digitalocean.coffee
+++ workers/social/lib/social/models/computeproviders/digitalocean.coffee
@@ -9,19 +9,16 @@

     { credential, instanceType, region } = options

-    @fetchCredentialData credential, (err, cred)->
+    # @fetchCredentialData credential, (err, cred)->
+    #   return callback err  if err?

-      return callback err  if err?
+    meta =
+      type   : "digitalocean"
+      image  : "ubuntu-13-10-x64"
+      region : region       ? "sfo1"
+      size   : instanceType ? "512mb"

-      meta =
-        type   : "digitalocean"
-        image  : "ubuntu-13-10-x64"
-        region : region       ? "sfo1"
-        size   : instanceType ? "512mb"
-
-      callback null, {
-        meta, credential: cred.publicKey
-      }
+    callback null, { meta, credential }

   @fetchAvailable = (client, options, callback)->


fix properties

fix properties

--- src/intermediate/TableView.coffee
+++ src/intermediate/TableView.coffee
@@ -1,9 +1,7 @@
 class @TableView
-  _data: []
-  _reverse: false
-
   constructor: (@$el, @model) ->
     @_data = @model.getData()
+    @reverse = false
     @$tbody = @$el.querySelector('.js-tbody')
     @$sortBtns = @$el.querySelectorAll('.js-btn-sort')
     @render()


Send more data to sentry about messages.reverse error

--- services/web/public/coffee/ide/chat/services/chatMessages.coffee
+++ services/web/public/coffee/ide/chat/services/chatMessages.coffee
@@ -50,6 +50,8 @@
 					chat.state.loading = false
 					if messages.length < MESSAGE_LIMIT
 						chat.state.atEnd = true
+					if !messages.reverse?
+						Raven?.captureException(new Error("messages has no reverse property"), { messages: messages })
 					messages.reverse()
 					prependMessages(messages)
 					chat.state.nextBeforeTimestamp = chat.state.messages[0]?.timestamp


Don't refresh duration when record is loading

--- assets/scripts/app/models/extensions.coffee
+++ assets/scripts/app/models/extensions.coffee
@@ -7,6 +7,6 @@
   ).property('_duration', 'finishedAt', 'startedAt')

   updateTimes: ->
-    if @get('stateManager.currentState.path') != 'rootState.loaded.reloading'
+    unless ['rootState.loaded.reloading', 'rootState.loading'].contains @get('stateManager.currentState.path')
       @notifyPropertyChange '_duration'
       @notifyPropertyChange 'finished_at'


proxy for images

proxy for images

--- tasks/test/karma.coffee
+++ tasks/test/karma.coffee
@@ -39,6 +39,8 @@
 			'xhr-polling'
 			'jsonp-polling'
 		]
+		proxies:
+			'/img': '/src/img'

 	karma.server.start options, (code) ->
 		if code > 0


Added Arabic locale tests

Added Arabic locale tests

--- tests/locales.test.coffee
+++ tests/locales.test.coffee
@@ -25,6 +25,19 @@
     it "shows the default CONFIRM translation", ->
       expect(@labels.confirm).to.equal "OK"

+  describe "Arabic", ->
+    beforeEach ->
+      @setLocale "ar"
+
+    it "shows the correct OK translation", ->
+      expect(@labels.ok).to.equal "موافق"
+
+    it "shows the correct CANCEL translation", ->
+      expect(@labels.cancel).to.equal "الغاء"
+
+    it "shows the correct CONFIRM translation", ->
+      expect(@labels.confirm).to.equal "تأكيد"
+
   describe "English", ->
     beforeEach ->
       @setLocale "en"


Client: Activity:Feed: Passed channel data to done function

--- client/activity/lib/util/transitionToChannel.coffee
+++ client/activity/lib/util/transitionToChannel.coffee
@@ -19,11 +19,11 @@
   # if we have an opened channel, switch to it immediately.
   if isChannelOpened
     threadActions.changeSelectedThread channel.id
-    done()
+    done null, channel
   else
     channelActions.loadChannelByName(channelName).then ({channel}) ->
       threadActions.changeSelectedThread channel.id
       channelActions.loadParticipants channel.id
-      done()
+      done null, channel