groups: wip tracing the fetchPermissions problem

--- client/landing/app/Applications/Groups.kdapplication/Views/permissionsmodal.coffee
+++ client/landing/app/Applications/Groups.kdapplication/Views/permissionsmodal.coffee
@@ -188,13 +188,11 @@
           color     : "#444444"
           diameter  : 12
         callback    : =>
-          group.updatePermissions(
-            @reducedList()
-            (err,res)=>
-              log 'updated permissions',err,res
-              @buttons["Save"].hideLoader()
-              # TODO: do something with this callback
-          )
+
+          group.updatePermissions @reducedList(), (err,res)=>
+            log 'updated permissions',err,res
+            @buttons["Save"].hideLoader()
+            # TODO: do something with this callback

     options.fields or= optionizePermissions permissionSet
     super options,data


Clean inrange checking if new game started

--- client.coffee
+++ client.coffee
@@ -22,9 +22,9 @@
 # ========== Events ==========
 exports.render = ->
 	log '8: FULL RENDER'
-	if not window.inRangeCheckinRunning?
+	if not (window.inRangeCheckinRunning?)
 		window.inRangeCheckinRunning = {}
-	if not inRangeCheckinRunning[Plugin.groupId()]?
+	if not (inRangeCheckinRunning[Plugin.groupId()]?)
 		inRangeCheckinRunning[Plugin.groupId()] = false
 	window.restoreMapLocation = true
 	log 'restoreMapLocation='+restoreMapLocation
@@ -41,6 +41,9 @@
 			Db.local.set 'gameNumber', remote
 			# Do cleanup stuff
 			Db.local.remove 'currentSetupPage'
+			if inRangeCheckinRunning[Plugin.groupId()]
+				clearInterval(checkinLocationFunction)
+				window.inRangeCheckinRunning[Plugin.groupId()] = false

 	# Ask for location
 	if !Geoloc.isSubscribed()


Made funny visible again (and removed unused code)

--- client.coffee
+++ client.coffee
@@ -47,8 +47,8 @@
 			Dom.style background: '#333', color: '#ffd700', overflow: 'hidden'
 		renderButton finalReached, Db.shared.ref('counters', App.userId()), buffer
 		#renderMagic localScores
-		Obs.observe !->
-			renderFunny() unless finalReached.get()
+	Obs.observe !->
+		renderFunny() unless finalReached.get()

 	Obs.observe !->
 		Db.shared.iterate 'counters', (counter) !-> started.set true
@@ -192,24 +192,6 @@
 				Db.shared.incr 'counters', App.userId(), bufferedClicks
 			buffering.set false

-renderMagic = (localScores) !->
-	Obs.observe !->
-		userCurrent = localScores.get('counters', App.userId())
-		if userCurrent >= FINALNUMBER and userCurrent < FINALNUMBER + 1000
-			Dom.div !->
-				Dom.animate
-						create:
-							opacity: 1 # target
-							initial:
-								opacity: 0
-						remove:
-							opacity: 0 # target
-							initial:
-								opacity: 1
-						content: !->
-						Dom.style fontSize: '150%', textAlign: 'center', padding: "30px 5px 15px"
-						Dom.text "I'll be damned, that is one absurd amount of clicks. Bloody well done, " + App.userName(App.userId()) + "! But remember: with great clicking power comes great clicking responsibility."
-
 renderFunny = !->
 	Obs.observe !->
 		funny = Db.personal.get('funnies', 'current')


:bug: Fix deprecated minimap method calls in quick settings

--- lib/minimap-quick-settings-view.coffee
+++ lib/minimap-quick-settings-view.coffee
@@ -1,4 +1,5 @@
 {View} = require 'atom'
+{CompositeDisposable} = require 'event-kit'
 $ = View.__super__.constructor

 Minimap = require './minimap'
@@ -15,14 +16,15 @@
   selectedItem: null

   initialize: (@minimapView) ->
+    @subscriptions = new CompositeDisposable
     @plugins = {}
-    @subscribe Minimap, 'plugin:added', ({name, plugin}) =>
+    @subscriptions.add Minimap.onDidAddPlugin ({name, plugin}) =>
       @addItemFor(name, plugin)
-    @subscribe Minimap, 'plugin:removed', ({name, plugin}) =>
+    @subscriptions.add Minimap.onDidRemovePlugin ({name, plugin}) =>
       @removeItemFor(name, plugin)
-    @subscribe Minimap, 'plugin:activated', ({name, plugin}) =>
+    @subscriptions.add Minimap.onDidActivatePlugin ({name, plugin}) =>
       @activateItem(name, plugin)
-    @subscribe Minimap, 'plugin:deactivated', ({name, plugin}) =>
+    @subscriptions.add Minimap.onDidDeactivatePlugin ({name, plugin}) =>
       @deactivateItem(name, plugin)

     @on 'core:move-up', @selectPreviousItem
@@ -30,7 +32,7 @@
     @on 'core:cancel', @destroy
     @on 'core:validate', @toggleSelectedItem

-    @subscribe @codeHighlights, 'mousedown', (e) =>
+    @codeHighlights.on 'mousedown', (e) =>
       e.preventDefault()
       @minimapView.setDisplayCodeHighlights(!@minimapView.displayCodeHighlights)
       @codeHighlights.toggleClass('active', @minimapView.displayCodeHighlights)
@@ -47,7 +49,8 @@
     @trigger('minimap:quick-settings-destroyed')
     @off()
     @hiddenInput.off()
-    @unsubscribe()
+    @codeHighlights.off()
+    @subscriptions.dispose()
     @detach()

   initList: ->


Install Atom TernJS

Install Atom TernJS

--- atom/packages.cson
+++ atom/packages.cson
@@ -4,6 +4,7 @@
   "aligner-php"
   "aligner-ruby"
   "aligner-scss"
+  "atom-ternjs"
   "autocomplete-clang"
   "autocomplete-elixir"
   "autocomplete-paths"


Add snippets for SCA getAbsoluteUrl functions

--- atom/snippets.cson
+++ atom/snippets.cson
@@ -375,6 +375,16 @@
     'prefix': 'debugger'
     'body': 'debugger;'

+  # SCA
+
+  'getAbsoluteUrl':
+    'prefix': 'getabsoluteurl'
+    'body': 'getAbsoluteUrl($1)$2'
+
+  'getThemeAbsoluteUrl':
+    'prefix': 'getthemeabsoluteurl'
+    'body': 'getThemeAbsoluteUrlOfNonManagedResources($1)$2'
+
   # SuiteScript

   'log execution':


Use 'fname' and 'vname' instead of 'name'

--- atom/snippets.cson
+++ atom/snippets.cson
@@ -133,7 +133,7 @@

   'function declaration':
     'prefix': 'fd'
-    'body': 'function ${1:name}($2) {\n\t$3\n}'
+    'body': 'function ${1:fname}($2) {\n\t$3\n}'

   'function expression':
     'prefix': 'fe'
@@ -263,7 +263,7 @@

   'function':
     'prefix': 'f'
-    'body': 'function ${1:name}($2) {\n\t$3\n}'
+    'body': 'function ${1:fname}($2) {\n\t$3\n}'

   'if':
     'prefix': 'if'
@@ -383,7 +383,7 @@

   'local variable':
     'prefix': 'local'
-    'body': 'local ${1:name}=${2:value}'
+    'body': 'local ${1:vname}=${2:value}'

   'if':
     'prefix': 'if'


add test script aginst tape

add test script aginst tape

--- atom/snippets.cson
+++ atom/snippets.cson
@@ -80,6 +80,11 @@
       }
     '''
 '.source.json':
+  'test':
+    'prefix': 'test#tape'
+    'body': '''
+      "test": "tape '{,!(node_modules)/**/}*.test.js' | faucet"
+    '''
   'xo':
     'prefix': 'xo'
     'body': '''


Redirect old routes

Redirect old routes

--- lib/routers/hardcoded_redirects.coffee
+++ lib/routers/hardcoded_redirects.coffee
@@ -34,6 +34,10 @@
   '/job/mobile-engineer': '/article/artsy-jobs-mobile-engineer'
   '/article/jesse-kedy-digital-marketing-manager-organic-growth-06-22-15': '/article/artsy-jobs-associate-director-of-organic-growth'
   '/feature/artsy-education': '/artsy-education'
+  '/favorites': '/user/saves#saved-artworks'
+  '/following/artists': '/user/saves#artists'
+  '/following/genes': '/user/saves#categories'
+  '/following/profiles': '/user/saves#galleries-institutions'

 for from, path of redirects
   router.get from, to(path)


Don't allow lobby leaves when in progress

--- server/lobbysystem/lobbies.coffee
+++ server/lobbysystem/lobbies.coffee
@@ -88,7 +88,7 @@
 @leaveLobby = (userId)->
   lobby = lobbies.find
     $or: [{creatorid: userId}, {"radiant._id": userId}, {"dire._id": userId}]
-    status: {$lt: 3}
+    status: {$lt: 2}
   lobby.forEach (l)->
     internalRemoveFromLobby(userId, l)
     maybeStopMatchmaking(userId, l)


refactoring sendMessages

refactoring sendMessages

--- server/methods/sendMessage.coffee
+++ server/methods/sendMessage.coffee
@@ -1,70 +1,90 @@
 Meteor.methods
-	sendMessage: (msg) ->
-		fromId = Meteor.userId()
-		# console.log '[methods] sendMessage -> '.green, 'fromId:', fromId, 'msg:', msg
+	sendMessage: (message) ->
+		console.log '[methods] sendMessage -> '.green, 'userId:', Meteor.userId(), 'arguments:', arguments

 		if not Meteor.userId()
 			throw new Meteor.Error('invalid-user', "[methods] sendMessage -> Invalid user")

+		if not Meteor.call 'canAccessRoom', message.rid, Meteor.userId()
+			return false
+
 		now = new Date()

-		retObj = null
-
-		roomData = ChatRoom.findOne msg.rid
-
-		rid = msg.rid
-
-		if not roomData
-			throw new Meteor.Error 'room-not-found', 'Sala não encontrada'
-
-		roomUpdate = { $set: { lm: now }, $inc: { msgs: 1 } }
-
-		if Meteor.userId() and not Meteor.user().username in roomData.usernames
-			roomUpdate.$push =
-				usernames:
-					$each: [ Meteor.user().username ]
-					$sort: 1
-
-		ChatRoom.update rid, roomUpdate
-
-		if Meteor.userId()?
-			messageFilter = { rid: rid, $and: [{ 'u._id': Meteor.userId() }], t: 't' }
-			activityFilter = { rid: rid, 'u._id': { $ne: Meteor.userId() } }

 		mentions = []
-		msg.message.replace /(?:^|\s|\n)(?:@)([A-Za-z0-9-_.]+)/g, (match, mention) ->
+		message.msg.replace /(?:^|\s|\n)(?:@)([A-Za-z0-9-_.]+)/g, (match, mention) ->
 			mentions.push mention
-
 		mentions = _.unique mentions
-
 		mentions = mentions.filter (mention) ->
 			return Meteor.users.findOne({username: mention}, {fields: {_id: 1}})?
-
 		mentions = mentions.map (mention) ->
 			return {
 				username: mention
 			}
-
 		if mentions.length is 0
 			mentions = undefined

-		msg = RocketChat.callbacks.run 'sendMessage', msg
+		message = RocketChat.callbacks.run 'sendMessage', message

-		ChatMessage.upsert messageFilter,
+
+		###
+		Defer other updated as their return is not interesting to the user
+		###
+		Meteor.defer ->
+
+			###
+			Update all the room activity tracker fields
+			###
+			ChatRoom.update
+				# only subscriptions to the same room
+				rid: message.rid
+			,
+				# update the last message timestamp
+				$set:
+					lm: now
+				# increate the messages counter
+				$inc:
+					msgs: 1
+
+			#ChatSubscription.update { rid: message.rid, 'u._id': { $ne: Meteor.userId() } }, { $inc: { unread: 1 }, $set: { ts: now } }, { multi: true } // only for mentioned
+
+
+			###
+			Update all other subscriptions to alert the their owners but witout incrementing
+			the unread counter, as it is only for mentions and direct messages
+			###
+			ChatSubscription.update
+				# only subscriptions to the same room
+				rid: message.rid
+				# only the ones that have not been alerted yet
+				alert: false
+				# not the msg owner
+				'u._id':
+					$ne: Meteor.userId()
+			,
+				$set:
+					# alert de user
+					alert: true
+					# open the room for the user
+					open: true
+			,
+				# make sure we alert all matching subscription
+				multi: true
+
+		ChatMessage.upsert
+				rid: message.rid
+				t: 't'
+				$and: [{ 'u._id': Meteor.userId() }]
+		,
 			$set:
 				'u._id': Meteor.userId()
 				'u.username': Meteor.user().username
 				ts: now
-				msg: msg.message
+				msg: message.msg
 				mentions: mentions
 			$unset:
 				t: 1
 				expireAt: 1
-
-		# increment unread counter on which user in room
-		Meteor.defer -> ChatSubscription.update activityFilter, { $inc: { unread: 1 }, $set: { ts: now } }, { multi: true }
-
-		return retObj

 	updateMessage: (msg) ->
 		fromId = Meteor.userId()
@@ -75,11 +95,11 @@

 		now = new Date()

-		messageFilter = { _id: msg.id, 'u._id': Meteor.userId() }
+		messageFilter = { _id: message.id, 'u._id': Meteor.userId() }

 		ChatMessage.update messageFilter,
 			$set:
 				ets: now
-				msg: msg.message
+				msg: message.msg

 		return


refactor

refactor

--- libraries/metrics/timeAsyncMethod.coffee
+++ libraries/metrics/timeAsyncMethod.coffee
@@ -16,15 +16,12 @@
 				"[Metrics] expected wrapped method '#{methodName}' to be invoked with a callback"
 			)

-		timer = new Metrics.Timer(key)
+		timer = new metrics.Timer(key)
 		start = new Date()

-		realMethod.call this, firstArgs, (callbackArgs...) ->
+		realMethod.call this, firstArgs..., (callbackArgs...) ->
 			timer.done()
 			elapsedTime = new Date() - start
 			if logger?
-				logger.log
-					key: key
-					time: elapsedTime
-					"[Metrics] timed async method call"
+				logger.log {key, elapsedTime}, "[Metrics] timed async method call"
 			callback.apply this, callbackArgs


Add drag and drop tip to example project.

--- server/user_initialization.coffee
+++ server/user_initialization.coffee
@@ -34,6 +34,17 @@
     submitted: new Date
     kind: 'source'
   source2Id = Sources.insert source2
+  source3 =
+    title: "Drag and drop"
+    description: "You can reorder sources, but also findings, risks, and
+      measures, by drag and drop. This also changes the order of the items
+      in the report."
+    projectId: projectId
+    userId: user._id
+    position: 2
+    submitted: new Date
+    kind: 'source'
+  source3Id = Sources.insert source3
   finding1 =
     title: "SAD is outdated"
     description: "This is an example of a finding. The software architecture


Continued work supporting in-memory "complicated" expression structures

--- math/expression_manipulation_spec.coffee
+++ math/expression_manipulation_spec.coffee
@@ -36,17 +36,43 @@
       it "provides the exponentiation its base", ->
         expect(@new_exp.first().base()).toBeAnEqualExpressionTo @exp_builder(10)

+    describe "on an expression that currently has an addition at the end", ->
+      it "'drops' the addition", ->
+        exp = @exp_builder(10, '+')
+        new_exp = @manip.exponentiate_last.build().invoke(exp)
+        expect(new_exp).toBeAnEqualExpressionTo @exp_builder('^': [10, null])

-    describe "on an expression that currently is an addition", ->
+    describe "on an expression that has a trailing exponent", ->
+      it "ignores the previous exponentiation", ->
+        exp = @exp_builder('^': [10, null])
+        new_exp = @manip.exponentiate_last.build().invoke(exp)
+        expect(new_exp).toBeAnEqualExpressionTo @exp_builder('^': [10, null])
+
+  describe "add number to end of expression", ->
+    describe "when the last element in the expression is a parenthesis", ->
       beforeEach ->
-        @exp = @exp_builder(10)
+        @exp = @exp_builder([1])

-      it "", ->
+      it "adds a multiplication symbol between elements", ->
+        exp = @manip.add_number_to_end.build(value: 11).invoke(@exp)
+        other = @exp_builder([1], '*', 11)
+        expect(exp).toBeAnEqualExpressionTo other
+
+
+    describe "when the command to be manipulated has an exponentiation ", ->
+      describe "with no power", ->
+        beforeEach ->
+          @exp = @exp_builder('^': [10, null])
+
+        it "inserts the number into the exponentiation", ->
+          new_exp = @manip.add_number_to_end.build(value: 11).invoke(@exp)
+          expected = @exp_builder({'^': [10,11]})
+          expect(new_exp).toBeAnEqualExpressionTo expected

   describe "(moved over from other test file)", ->
     describe "the square command", ->
       beforeEach ->
-        @square = @math.commands.square.build()
+        @square = @manip.square.build()

       it_plays_command_role (test)->
         test.square


Updated remaining expression manipulation types

--- math/expression_manipulation_spec.coffee
+++ math/expression_manipulation_spec.coffee
@@ -5,7 +5,7 @@
 it_plays_manipulation_role = (subject, math)->
   describe "acting as an expression manipulation", ->
     beforeEach ->
-      @subject = subject(@)
+      @subject = subject.call(@)

     it "has an invoke method", ->
       expect(typeof @subject.invoke).toEqual "function"
@@ -18,7 +18,7 @@
 it_inserts_component_into_the_last_nested_open_expression = (specifics)->
   it "adds #{specifics.name} to the deepest final open expression", ->
     @exp = @exp_builder(open_expression: {open_expression: []})
-    exp = specifics.manipulator(@).invoke(@exp)
+    exp = specifics.manipulator.call(@).invoke(@exp)
     expected = specifics.should_equal_after(@exp_builder)
     expect(exp).toBeAnEqualExpressionTo expected

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

   describe "appending multiplication", ->
     it_plays_manipulation_role (test)->
-      test.manip.append_multiplication.build()
+      @manip.append_multiplication.build()

     it "adds multiplication to the end of the expression", ->
       exp = @exp_builder(1)
@@ -141,13 +141,28 @@

     it_inserts_component_into_the_last_nested_open_expression(
       name: 'multiplication'
-      manipulator: (test)-> test.manip.append_multiplication.build()
+      manipulator: -> @manip.append_multiplication.build()
       should_equal_after: (builder)-> builder(open_expression: {open_expression: ['*']}))

+  describe "appending division", ->
+    it_plays_manipulation_role (test)->
+      @manip.append_division.build()
+
+    it "adds a division to the expression", ->
+      exp = @exp_builder(1)
+
+      new_exp = @manip.append_division.build().invoke(exp)
+      expect(new_exp.last() instanceof @math.components.division).toBeTruthy()
+
+    it_inserts_component_into_the_last_nested_open_expression(
+      name: 'division'
+      manipulator: -> @manip.append_division.build()
+      should_equal_after: (builder)-> builder(open_expression: {open_expression: ['/']}))
+

   describe "appending addition", ->
     it_plays_manipulation_role (test)->
-      test.manip.append_addition.build()
+      @manip.append_addition.build()

     it "adds addition to the end of the expression", ->
       exp = @exp_builder(1)
@@ -156,8 +171,40 @@

     it_inserts_component_into_the_last_nested_open_expression(
       name: 'addition'
-      manipulator: (test)-> test.manip.append_addition.build()
+      manipulator: -> @manip.append_addition.build()
       should_equal_after: (builder)-> builder(open_expression: {open_expression: ['+']}))
+
+
+
+  describe "appending subtraction", ->
+    it_plays_manipulation_role (test)->
+      @manip.append_subtraction.build()
+
+    it "adds subtraction to the end of the expression", ->
+      exp = @exp_builder(1)
+      new_exp = @manip.append_subtraction.build().invoke(exp)
+      expect(new_exp.last() instanceof @math.components.subtraction).toEqual true
+
+    it_inserts_component_into_the_last_nested_open_expression(
+      name: 'subtraction'
+      manipulator: -> @manip.append_subtraction.build()
+      should_equal_after: (builder)-> builder(open_expression: {open_expression: ['-']}))
+
+  describe "appending a decimal", ->
+    it_plays_manipulation_role ->
+      @manip.append_decimal.build(value: 5)
+
+    it "correctly adds a decimal to the value", ->
+      exp = @math.expression.build()
+      exp = @manip.append_number.build(value: 1).invoke(exp)
+      exp = @manip.append_decimal.build().invoke(exp)
+      exp = @manip.append_number.build(value: 1).invoke(exp)
+      @expect_value(exp, '1.1')
+
+    it_inserts_component_into_the_last_nested_open_expression(
+      name: 'decimal'
+      manipulator: -> @manip.append_decimal.build()
+      should_equal_after: (builder)-> builder(open_expression: {open_expression: ['0.']}))

   describe "square expression", ->
@@ -165,48 +212,21 @@
       @square = @manip.square.build()

     it_plays_manipulation_role (test)->
-      test.square
+      @square

     it "returns a squared expression", ->
       exp = @exp_builder(10)
       squared = @square.invoke(exp)
       @expect_value(squared, '100')

-
   describe "(moved over from other test file)", ->
-
-    describe "the decimal manipulation", ->
-      it "correctly adds a decimal to the value", ->
-        exp = @math.expression.build()
-        exp = @manip.append_number.build(value: 1).invoke(exp)
-        exp = @manip.decimal.build().invoke(exp)
-        exp = @manip.append_number.build(value: 1).invoke(exp)
-        @expect_value(exp, '1.1')
-
     describe "NumberManipulation", ->
       it_plays_manipulation_role (test)->
-        test.manip.append_number.build(value: 5)
-
-    describe "DecimalManipulation", ->
-      it_plays_manipulation_role (test)->
-        test.manip.decimal.build(value: 5)
-
-    describe "SubtractionManipulation", ->
-      it_plays_manipulation_role (test)->
-        test.manip.subtraction.build()
-
-      it "adds subtraction to the end of the expression", ->
-        exp = @math.expression.buildWithContent([
-          @math.components.number.build(value: '1')
-        ])
-
-        new_exp = @manip.subtraction.build().invoke(exp)
-
-        expect(new_exp.last() instanceof @math.components.subtraction).toEqual true
+        @manip.append_number.build(value: 5)

     describe "NegationManipulation", ->
       it_plays_manipulation_role (test)->
-        test.manip.negate_last.build()
+        @manip.negate_last.build()

       it "will negate the last element", ->
         exp = @math.expression.buildWithContent([
@@ -215,20 +235,10 @@
         new_exp = @manip.negate_last.build().invoke(exp)
         expect(new_exp.last().value()).toEqual -1

-    describe "DivisionManipulation", ->
-      it_plays_manipulation_role (test)->
-        test.manip.division.build()
-
-      it "adds a division to the expression", ->
-        exp = @math.expression.buildWithContent([
-          @math.components.number.build(value: '1')
-        ])
-        new_exp = @manip.division.build().invoke(exp)
-        expect(new_exp.last() instanceof @math.components.division).toBeTruthy()

     describe "PiManipulation", ->
       it_plays_manipulation_role (test)->
-        test.manip.pi.build()
+        @manip.pi.build()

       it "adds a mulitplication and pi to the expression", ->
         exp = @math.expression.buildWithContent([
@@ -240,7 +250,7 @@

     describe "SquareRootManipulation", ->
       it_plays_manipulation_role (test)->
-        test.manip.square_root.build()
+        @manip.square_root.build()

       it "finds the square root of an expression", ->
         exp = @math.expression.buildWithContent([


add group create controller and save created group data in one go

--- www/js/controllers/group.coffee
+++ www/js/controllers/group.coffee
@@ -15,8 +15,8 @@
 			url: "/create"
 			views:
 				groupContent:
-					templateUrl: 'templates/group/update.html'
-					controller: 'GroupUpdateCtrl'
+					templateUrl: 'templates/group/create.html'
+					controller: 'GroupCreateCtrl'
 			resolve:
 				resource: 'resource'
 				model: (resource) ->
@@ -144,7 +144,7 @@
 			resource:	resource
 			model:		model

-	.controller 'GroupUpdateCtrl', ($scope, $state, $log, resource, model) ->
+	.controller 'GroupCreateCtrl', ($scope, $state, $log, resource, model) ->
 		_.extend $scope,
 			resource:	resource
 			model: 		model
@@ -157,9 +157,35 @@
 				if ret
 					form.$show()
 					form.$setError 'model.name', 'This is a required field'
-				return ret
+				return not ret
+			save:  ->
+				model
+					.$save()
+					.then ->
+						$state.go 'app.roster.list'
+					.catch (err) ->
+						$log.error _.first(_.first(_.values(err.data.invalidAttributes))).message
+
+		$scope.$on 'cropImg.completed', (event, outImg) ->
+			model.photo = outImg
+			model.photoUrl = outImg
+
+	.controller 'GroupUpdateCtrl', ($scope, $log, resource, model) ->
+		_.extend $scope,
+			resource:	resource
+			model: 		model
+			users:		resource.Users.instance()
+			select: (files) ->
+				if files?.length != 0
+					$scope.$emit 'cropImg', URL.createObjectURL(files[0])
+			valid: (form) ->
+				ret = (not model.name?) or model?.name == ''
+				if ret
+					form.$show()
+					form.$setError 'model.name', 'This is a required field'
+				return not ret
 			save: (form) ->
-				if not $scope.valid form
+				if $scope.valid form
 					model
 						.$save()
 						.catch (err) ->


check if mathjax exists before proceeding

--- lms/static/coffee/src/customwmd.coffee
+++ lms/static/coffee/src/customwmd.coffee
@@ -1,6 +1,9 @@
 # Mostly adapted from math.stackexchange.com: http://cdn.sstatic.net/js/mathjax-editing-new.js

 $ ->
+
+  if not MathJax?
+    return

   HUB = MathJax.Hub


Add whitespace option for skipScenario

Add whitespace option for skipScenario

--- cakes.coffee
+++ cakes.coffee
@@ -82,6 +82,8 @@
 skipScenario = (message, options)->
 	if 'label' in options
 		message = '(skipped) ' + message
+	if 'whitespace' in options
+		message = '\n    '+message
 	if 'style' in options
 		message = message.yellow.bold


Added Pirate Ship

Added Pirate Ship

--- cards.coffee
+++ cards.coffee
@@ -1165,6 +1165,41 @@
   costPotion: 1
   getCoins: (state) ->
     Math.floor((state.current.draw.length + state.current.discard.length) / 5)
+}
+
+makeCard 'Pirate Ship', action, {
+  cost: 4
+  isAttack: true
+
+  playEffect: (state) ->
+    choice = state.current.ai.choose('pirateShip', state, ['coins', 'attack'])
+
+    if choice is 'coins'
+      state.current.coins += state.current.mats.pirateShip
+      state.log("...getting +$#{state.current.mats.pirateShip}.")
+
+    else if choice is 'attack'
+      state.log("...attacking the other players.")
+      attackSuccess = false
+
+      state.attackOpponents (opp) ->
+        drawn = opp.getCardsFromDeck(2)
+        state.log("...#{opp.ai} reveals #{drawn}.")
+        drawnTreasures = []
+        for card in drawn
+          if card.isTreasure
+            drawnTreasures.push(card)
+        treasureToTrash = state.current.ai.choose('trashOppTreasure', state, drawnTreasures)
+        if treasureToTrash
+          attackSuccess = true
+          drawn.remove(treasureToTrash)
+          state.log("...#{state.current.ai} trashes #{opp.ai}'s #{treasureToTrash}.")
+        state.current.discard.concat (drawn)
+        state.log("...#{opp.ai} discards #{drawn}.")
+
+      if attackSuccess
+        state.current.mats.pirateShip += 1
+        state.log("...#{state.current.ai} takes a Coin token (#{state.current.mats.pirateShip} on the mat).")
 }

 makeCard 'Princess', action, {


Added Thief.

Added Thief.

--- cards.coffee
+++ cards.coffee
@@ -1286,6 +1286,30 @@
         {trash: 2}
       ])
       applyBenefit(state, benefit)
+}
+
+makeCard 'Thief', action, {
+  cost: 4
+  isAttack: true
+
+  playEffect: (state) ->
+    state.attackOpponents (opp) ->
+      drawn = opp.getCardsFromDeck(2)
+      state.log("...#{opp.ai} reveals #{drawn}.")
+      drawnTreasures = []
+      for card in drawn
+        if card.isTreasure
+          drawnTreasures.push(card)
+      treasureToTrash = state.current.ai.choose('trashOppTreasure', state, drawnTreasures)
+      if treasureToTrash
+        drawn.remove(treasureToTrash)
+        state.log("...#{state.current.ai} trashes #{opp.ai}'s #{treasureToTrash}.")
+        cardToGain =  state.current.ai.chooseGain(state, [treasureToTrash, null])
+        if cardToGain
+          state.gainCard(state.current, cardToGain, 'discard', true)
+          state.log("...#{state.current.ai} gains the trashed #{treasureToTrash}.")
+      state.current.discard.concat (drawn)
+      state.log("...#{opp.ai} discards #{drawn}.")
 }

 makeCard 'Tournament', action, {


set left and right shaders correctly

set left and right shaders correctly

--- oriDomi.coffee
+++ oriDomi.coffee
@@ -203,12 +203,12 @@
     vMask.style.height = '100%'

     if @shading
-      leftShader = shader.cloneNode()
+      leftShader = vMask.getElementsByClassName('oridomi-shader-top')[0]
       leftShader.className = 'oridomi-shader-left'
-      rightShader = shader.cloneNode()
+      leftShader.style.background = '-webkit-linear-gradient(left, rgba(0, 0, 0, .5) 0%, rgba(255, 255, 255, .35) 100%)'
+      rightShader = vMask.getElementsByClassName('oridomi-shader-bottom')[0]
       rightShader.className = 'oridomi-shader-right'
-      vMask.appendChild leftShader
-      vMask.appendChild rightShader
+      rightShader.style.background = '-webkit-linear-gradient(right, rgba(0, 0, 0, .5) 0%, rgba(255, 255, 255, .35) 100%)'

     vPanel = hPanel.cloneNode()
     vPanel.className = 'oridomi-panel-v'


Guaranteeing operator order by using parens

--- test/fuzz/generator.coffee
+++ test/fuzz/generator.coffee
@@ -27,7 +27,7 @@

 		operator = rand.sample(OPERATORS)

-		"#{operator} #{operand}"
+		"(#{operator} #{operand})"

 generate['BinaryExpression'] = do ->
@@ -39,7 +39,7 @@

 		operator = rand.sample(OPERATORS)

-		"#{left} #{operator} #{right}"
+		"(#{left} #{operator} #{right})"

 generate['Expression'] = do ->


ADD: init iframe if button not exist

ADD: init iframe if button not exist

--- app/assets/javascripts/media_lib/jquery.nmdUploader.js.coffee
+++ app/assets/javascripts/media_lib/jquery.nmdUploader.js.coffee
@@ -69,18 +69,22 @@
         # Инициализируем iframe с загрузчиком
         $uploaderIframe = $uploader.find('.js-uploader-iframe')
         if $uploaderIframe.length
-          $uploaderIframeOpen = $uploader.find('.js-uploader-iframe-open')
           iframeTemplate = _.template '''
             <iframe width='<%= width %>' height='<%= height %>' src='<%= src %>' frameborder='0'></iframe>
           '''
-          $uploaderIframeOpen.on 'click', ->
-            $uploaderIframe.each ->
+          initIframes = ($iframes) ->
+            $iframes.each ->
               $iframe = $ @
               iframeData = $iframe.data()
               $iframe.html iframeTemplate
                 src: getUrl()
                 width: iframeData.width or '100%'
                 height: iframeData.height or 500
+          $uploaderIframeOpen = $uploader.find('.js-uploader-iframe-open')
+          if $uploaderIframeOpen.length
+            $uploaderIframeOpen.on 'click', -> initIframes($uploaderIframe)
+          else
+            initIframes($uploaderIframe)

     select: (options) ->
       settings = _.extend {}, options


including ComprobanteOnFormMixin on VentasNewView

--- app/assets/javascripts/views/ventas/ventas_new_view.js.coffee
+++ app/assets/javascripts/views/ventas/ventas_new_view.js.coffee
@@ -1,2 +1,6 @@
-Bandango.VentasNewView = Bandango.ModelBackedView.extend
+Bandango.VentasNewView = Bandango.ModelBackedView.extend Bandango.GravatarImagenOnForm, Bandango.ClienteOnFormMixin, Bandango.ComprobanteOnFormMixin,
   bindAttributesTo: null
+
+  submit: ->
+    console.log "submit!"
+    false


Update the test descriptions to match the tests

--- test/remote-channel.coffee
+++ test/remote-channel.coffee
@@ -19,7 +19,7 @@

 testify.test "A remote channel", (context) ->

-  context.test "can send and receive events", (context) ->
+  context.test "can send and receive messages", (context) ->

     sender = make()
     receiver = make()
@@ -30,7 +30,7 @@

       receiver.receive (message) ->

-        context.test "using an 'on' handler", ->
+        context.test "using a recieve handler", ->
           assert.ok message.content is "Dan"
           receiver.end()


winston logger added

winston logger added

--- backend/app.coffee
+++ backend/app.coffee
@@ -55,5 +55,4 @@
 # Check whether user exists or not
 app.get '/api/checkuser/:username', routes.checkUser

-# here and in similar places to use winston or something like this instead of using console.log
 winston.info "API is starting on port #{PORT}"


No ned for the hack anymore

No ned for the hack anymore

--- modules/player/util.coffee
+++ modules/player/util.coffee
@@ -86,22 +86,12 @@
       resolve(info)

   # Uses youtube-dl to get information of an URL or search term
-  getInfo: (query)=>
-    info = await ytdl(query, [
+  getInfo: (query)=> await ytdl(query, [
       '--default-search=ytsearch'
       '--ignore-errors'
       '--force-ipv4',
       '--format=bestaudio/best'
     ])
-    # temporary hack, i hope
-    unless info.partial or info.items.length
-      info = await ytdl(query, [
-        '--default-search=ytsearch'
-        '--ignore-errors'
-        '--force-ipv4',
-        '--format=bestaudio/best'
-      ])
-    return info

   # Parses a list of filters (| speed=2 distort, etc)


add pause after lines mode

add pause after lines mode

--- app/vis.coffee
+++ app/vis.coffee
@@ -427,6 +427,8 @@
         if next_child?
           # fadeToArray(1, 1000) next_child._text_object.children
           @panCameraToObject next_child._text_object
+      .then =>
+        @wait 3e3
       .then =>
         if next_child?
           return @traverse next_child


Fix oclc_href causing DataTables warnings

--- js/book-aligner.coffee
+++ js/book-aligner.coffee
@@ -64,8 +64,8 @@
             _.values(data.records)[0].titles[0],
             _.values(data.records)[0].publishDates[0],
             ht_object.enumcron || '',
-            null,
-            oclc_href(_.values(data.records)[0].oclcs[0]),
+            '',
+            oclc_href(_.values(data.records)[0].oclcs[0]) || '',
             score
           ]).draw(false)
           $('#table').DataTable().columns.adjust().draw()
@@ -119,7 +119,7 @@
             data.metadata.year || '',
             data.metadata.volume || '',
             data.metadata.imagecount || '',
-            oclc_href(data.metadata['oclc-id']),
+            oclc_href(data.metadata['oclc-id']) || '',
             score
           ]).draw(false)
           $('#table').DataTable().columns.adjust().draw()
@@ -174,7 +174,7 @@
             data.volumeInfo.publishedDate,
             '',
             data.volumeInfo.pageCount || '',
-            null,
+            '',
             score
           ]).draw(false)
           $('#table').DataTable().columns.adjust().draw()


Added test for headers and footers

Added test for headers and footers

--- test/14_word_file_test.coffee
+++ test/14_word_file_test.coffee
@@ -20,3 +20,13 @@
         body = result.getBody()
         expect(body).to.match new RegExp('Moli\\u00e8re')
         done()
+
+  it 'should have headers and footers', (done) ->
+    extract = extractor.extract path.resolve(__dirname, "data/test04.doc")
+    expect(extract).to.be.fulfilled
+    extract
+      .then (result) ->
+        body = result.getHeaders()
+        expect(body).to.match new RegExp('The footer')
+        expect(body).to.match new RegExp('Moli\\u00e8re')
+        done()


fix wkbwriter test

fix wkbwriter test

--- test/wkbwriter-test.coffee
+++ test/wkbwriter-test.coffee
@@ -18,8 +18,8 @@
     "a new instance should be an instance of WKTWriter": (writer) ->
       assert.instanceOf writer, WKBWriter

-    "should have a write function": (writer) ->
-      assert.isFunction writer.write
+    "should have a writeHEX function": (writer) ->
+      assert.isFunction writer.writeHEX

     "should write a Polygon to WKB in hex format": (writer) ->
       assert.equal (writer.writeHEX reader.read "POINT(1 2)"),


Fixes precise floating point math in equation builder #3809068

--- math/expression_evaluation_spec.coffee
+++ math/expression_evaluation_spec.coffee
@@ -15,6 +15,7 @@
     builder_lib = ttm.require('lib/math/build_expression_from_javascript_object')
     @exp = @math.object_to_expression.buildExpressionFunction()
     @expect_evaluation = expect_evaluation
+
   it "evaluates a simple addition", ->
     exp = @exp(2, '+', 7)
     results = @evaluation.build(exp).resultingExpression()
@@ -48,3 +49,14 @@
       '8'
     )

+  it "evaluates this expression correctly", ->
+    @expect_evaluation(
+      [
+        [
+          "41", '/', "32.75", '-', '1'
+        ], '*', '100'
+      ],
+      '25.19'
+    )
+
+


Support optional dash in CoffeeScript fence name

--- src/packages/gfm/grammars/gfm.cson
+++ src/packages/gfm/grammars/gfm.cson
@@ -55,7 +55,7 @@
     'name': 'comment.hr.gfm'
   }
   {
-    'begin': '^`{3,}\\s*coffee(script)?$'
+    'begin': '^`{3,}\\s*coffee-?(script)?$'
     'beginCaptures':
       '0': 'name': 'support.gfm'
     'end': '^`{3,}$'


Use action-log/output-url in the UI

Use action-log/output-url in the UI

--- js/inner/action_log.coffee
+++ js/inner/action_log.coffee
@@ -120,8 +120,12 @@
   maybe_retrieve_output: () =>
     if @has_output() and !@retrieved_output() and !@retrieving_output()
       @retrieving_output(true)
+      url = if @output_url
+              @output_url
+            else
+              "/api/v1/project/#{@build.project_name()}/#{@build.build_num}/output/#{@step()}/#{@index()}"
       $.ajax
-        url: "/api/v1/project/#{@build.project_name()}/#{@build.build_num}/output/#{@step()}/#{@index()}"
+        url: url
         type: "GET"
         success: (data) =>
           @retrieved_output(true)


fix #182: handle series with 0 or 1 values

--- app/graph.coffee
+++ app/graph.coffee
@@ -320,11 +320,14 @@
         i = t.bisector.left t.values, time, 1
         d0 = t.values[i - 1]
         d1 = t.values[i]
-        if time - d0.time > d1.time - time then d1 else d0
+        if d0 and d1
+          if time - d0.time > d1.time - time then d1 else d0
+        else if d0
+          d0

       legend.selectAll('.crosshair-value')
         .data(target_values)
-        .text((d) -> d.value)
+        .text((d) -> d?.value)

     mouse_position.onValue (p) ->
       positionCrosshair p.x, p.time


Change Chinese (Traditional) location

Change Chinese (Traditional) location

--- app/index.coffee
+++ app/index.coffee
@@ -51,7 +51,7 @@
 languageManager = new LanguageManager
   translations:
     en: label: 'English', strings: enUs
-    "zh-hant": label: "繁體中文 Chinese (Traditional)", strings: './translations/zh-hant.json'
+    "zh-tw": label: "繁體中文 Chinese (Traditional)", strings: './translations/zh-tw.json'

 languageManager.on 'change-language', (e, code, strings) ->
   translate.load strings


analytics

analytics

--- app/index.coffee
+++ app/index.coffee
@@ -18,6 +18,7 @@
 Config = require('lib/config')

 ZooniverseBar = require('zooniverse/lib/controllers/top_bar')
+googleAnalytics = require('zooniverse/lib/google_analytics')

 Archive = require('models/Archive')
@@ -91,6 +92,8 @@
       app: 'notes_from_nature'
       appName:'Notes From Nature'

+    googleAnalytics.init account: 'UA-1224199-39', domain: 'www.notesfromnature.org'
+
     Spine.Route.setup()

     Badge.loadDefinitions()


update package keymap comment

update package keymap comment

--- keymaps/agda-mode.cson
+++ keymaps/agda-mode.cson
@@ -1,12 +1,12 @@
 # Keybindings require three things to be fully defined: A selector that is
 # matched against the focused element, the keystroke and the command to
 # execute.
-#}
+#
 # Below is a basic keybinding which registers on all platforms by applying to
 # the root workspace element.

 # For more detailed documentation see
-# https://atom.io/docs/latest/advanced/keymaps
+# https://atom.io/docs/latest/behind-atom-keymaps-in-depth
 'atom-text-editor.agda':
     # Global commands
     'ctrl-c        ctrl-l': 'agda-mode:load'


Create keymaps for other platforms.

Create keymaps for other platforms.

--- keymaps/atom-repl.cson
+++ keymaps/atom-repl.cson
@@ -7,8 +7,14 @@

 # For more detailed documentation see
 # https://atom.io/docs/latest/behind-atom-keymaps-in-depth
-'atom-text-editor':
+'.platform-darwin atom-text-editor':
   'cmd-alt-enter': 'hydrogen:run'

-'atom-workspace':
+'.platform-darwin atom-workspace':
   'cmd-alt-backspace': 'hydrogen:clear-results'
+
+'.platform-win32 atom-text-editor, .platform-linux atom-text-editor':
+  'ctrl-alt-enter': 'hydrogen:run'
+
+'.platform-win32 atom-workspace, .platform-linux atom-workspace':
+  'ctrl-backspace': 'hydrogen:clear-results'


new less

new less

--- build-script/util.coffee
+++ build-script/util.coffee
@@ -102,6 +102,8 @@
     callback 1
     return

+   cssCode = cssCode.css
+
    if options.map
     url = "#{map.substr BUILD.length}"
     cssCode = "#{cssCode}\n/*# sourceMappingURL=#{url}*/\n"


String#extension

String#extension

--- test/string_extensions.coffee
+++ test/string_extensions.coffee
@@ -21,7 +21,7 @@

   equals "jquery.min.js".extension(), "js"

-
+  equals ""

 test "#humanize", ->
   equals "employee_salary".humanize(), "Employee salary"


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/string_extensions.coffee
+++ test/string_extensions.coffee
@@ -32,7 +32,7 @@
   ok ''.parse() == '', "Empty string parses to exactly the empty string"

 test "#startsWith", ->
-  eq
+  ok "cool".startsWit

 test "#titleize", ->
   equals "man from the boondocks".titleize(), "Man From The Boondocks"


Remove unnecessary extraction

Remove unnecessary extraction

--- api/routes/tags.coffee
+++ api/routes/tags.coffee
@@ -18,20 +18,13 @@
     app.post '/tags'
     , middleware.requireMinPrivilegeLevel(privilegeLevels.editor)
     , (req, res, next) ->
-        {
-            name,
-            parent,
-            icon,
-            priority
-        } = req.body
-
-        if !name?
+        if !req.body.name?
             res.status 400
             .json { status: 400, error: 'name field missing' }
             return

         db.run 'INSERT INTO tTag (name, parent, icon, priority) VALUES (?, ?, ?, ?)',
-            name, parent, icon, priority, (err) ->
+            req.body.name, req.body.parent, req.body.icon, req.body.priority, (err) ->
                 if err?
                     if err.code == 'SQLITE_CONSTRAINT'
                         res.status 400


Navigate to the right place after saving a quote

--- src/client/views/quote_form_view.coffee
+++ src/client/views/quote_form_view.coffee
@@ -3,7 +3,7 @@
     template: JST['quote_form']

     initialize: =>
-      @listenTo @model, 'sync', => Backbone.history.navigate Path.quotes(), true
+      @listenTo @model, 'sync', => Backbone.history.navigate Path.ism(@model.get 'ism_id'), true

     context: =>
       cid: @cid


:bomb: Don't escape insert-mode when autocomplete-plus is active #339

By this change escape is no longer trigger activate-normal-mode when
autocomplete-active is set in classList

--- keymaps/vim-mode-plus.cson
+++ keymaps/vim-mode-plus.cson
@@ -15,7 +15,7 @@

 # all except normal
 # -------------------------
-'atom-text-editor.vim-mode-plus:not(.normal-mode)':
+'atom-text-editor.vim-mode-plus:not(.normal-mode):not(.autocomplete-active)':
   'escape': 'vim-mode-plus:activate-normal-mode'
   'ctrl-[': 'vim-mode-plus:activate-normal-mode'


Replaced indexOf shim with jQuery built-in inArray

--- source/coffee/image-picker.coffee
+++ source/coffee/image-picker.coffee
@@ -11,12 +11,6 @@
       select.data "picker", new ImagePicker(this, sanitized_options(opts))
       opts.initialized() if opts.initialized?
 })
-
-# Array.indexOf shim for <= IE 8
-Array::indexOf or= (item) ->
-  for x, i in this
-    return i if x is item
-  return -1

 sanitized_options = (opts) ->
   default_options = {
@@ -89,8 +83,8 @@
     old_values = @selected_values()
     if @multiple
       if imagepicker_option.value() in @selected_values()
-        new_values = @selected_values()
-        new_values.splice(old_values.indexOf(imagepicker_option.value()), 1)
+        new_values = @selected_values()
+        new_values.splice( jQuery.inArray(old_values, imagepicker_option.value()), 1)
         @select.val []
         @select.val new_values
       else


fix https://github.com/sagemathinc/smc/issues/765 -- parse_bup_timestamp fails on OS X

--- src/smc-util/test/misc-test.coffee
+++ src/smc-util/test/misc-test.coffee
@@ -846,8 +846,7 @@
         input = "2014-01-02-031508"
         act = misc.parse_bup_timestamp("2014-01-02-031508")
         act.should.be.instanceOf Date
-        # month starts at 0, but not the day?
-        exp = new Date(2014, 0, 2, 3, 15, 8, 0)
+        exp = new Date('2014-01-02T03:15:08.000Z')
         act.should.be.eql exp

 describe "hash_string", ->


fix https://github.com/sagemathinc/smc/issues/678 - latex editor -- save custom command on losing focus (not just hitting enter)

--- src/smc-webapp/editor_latex.coffee
+++ src/smc-webapp/editor_latex.coffee
@@ -68,11 +68,13 @@
         @log.find("a").tooltip(delay:{ show: 500, hide: 100 })
         @_pages['log'] = @log
         @log_input = @log.find("input")
+        save_custom_build_command = () =>
+            @set_conf_doc(latex_command: @log_input.val())
+            @save()
         @log_input.keyup (e) =>
             if e.keyCode == 13
-                latex_command = @log_input.val()
-                @set_conf_doc(latex_command: latex_command)
-                @save()
+                save_custom_build_command()
+        @log_input.on('blur', save_custom_build_command)

         @errors = @element.find(".salvus-editor-latex-errors")
         @_pages['errors'] = @errors


webapp/latex/Rnw: do not freak out when there is no concordance file

--- src/smc-webapp/editor_latex.coffee
+++ src/smc-webapp/editor_latex.coffee
@@ -461,6 +461,7 @@
             (cb) =>
                 if @latex_editor.has_uncommitted_changes()
                     delete @_last_update_preview  # running latex on stale version
+                    @get_rnw_concordance_error = false
                 @run_latex
                     command : @load_conf_doc().latex_command
                     cb      : cb
@@ -719,6 +720,8 @@
         return ret

     get_rnw_concordance: (cb) =>
+        if @get_rnw_concordance_error
+            cb(); return
         # always call the cb without an error -- otherwise the errors don't show up at all
         if @preview.pdflatex.ext == 'rnw'
             conc_fn = @preview.pdflatex.base_filename + '-concordance.tex'
@@ -728,9 +731,8 @@
                 cb         : (err, res) =>
                     err ?= res.error
                     if err
-                        alert_message
-                            type    : "error"
-                            message : "Unable to read concordance file #{conc_fn} -- #{err}"
+                        console.warn("Unable to read concordance file #{conc_fn} -- #{misc.to_json(err)}")
+                        @get_rnw_concordance_error = true
                     else
                         # concordance file is explained here:
                         # https://cran.r-project.org/web/packages/patchDVI/vignettes/patchDVI.pdf


Escaping CR, LF and TAB

Escaping CR, LF and TAB

--- src/compilers/OptimizingCompiler.coffee
+++ src/compilers/OptimizingCompiler.coffee
@@ -102,11 +102,22 @@
 	',': (x, y, dir, index, stack) ->
 		if stack.length
 			char = String.fromCharCode stack.pop()
-			if char == "'"
-				char = "\\'"
-			else if char == '\\'
-				char = '\\\\'
-			"/* , */  programState.out('#{char}')"
+
+			safeChar = switch char
+				when "'"
+					"\\'"
+				when '\\'
+					'\\\\'
+				when '\n'
+					'\\n'
+				when '\r'
+					'\\r'
+				when '\t'
+					'\\t'
+				else
+					char
+
+			"/* , */  programState.out('#{safeChar}')"
 		else
 			'/* , */  programState.out(String.fromCharCode(programState.pop()))'


Correct extra vertices when clipping a polygon

--- app/js/geometry.coffee
+++ app/js/geometry.coffee
@@ -157,27 +157,32 @@
   constrainPolygonToContainer: (vertices, container) ->
     for constraint in Utils.sequentialPairs container
       clipped   = []
-      # don't need an actually normalized normal, just the direction.
-      normal    = Vec.perpendicular Vec.sub constraint[1], constraint[0]
+      normal    = Vec.perpendicularNormal Vec.sub constraint[1], constraint[0]
       reference = constraint[0]

       for [prev, vertex] in Utils.sequentialPairs vertices
         test     = Vec.sub vertex, reference
         prevTest = Vec.sub prev, reference
-        if Vec.dotProduct(test, normal) >= 0 # vertex is inside the container
-          if Vec.dotProduct(prevTest, normal) < 0 # prev is outside container
+        if Vec.dotProduct(test, normal) > 0 # vertex is inside the container
+          if Vec.dotProduct(prevTest, normal) <= 0 # prev is outside container
             clipped.push @lineSegmentIntersection prev, vertex, reference, normal
           clipped.push vertex
-        else if Vec.dotProduct(prevTest, normal) >= 0 # prev is inside container
+        else if Vec.dotProduct(prevTest, normal) > 0 # prev is inside container
           clipped.push @lineSegmentIntersection prev, vertex, reference, normal
       vertices = clipped
-    vertices
+    @coalesceVertices vertices # precision errors lead to extra vertices, sometimes

   # intersection point of a segment from A to B to with the line defined by a point and a normal.
   lineSegmentIntersection: (vertexA, vertexB, point, normal) ->
     direction = Vec.sub vertexB, vertexA
     distance  = Vec.dotProduct(Vec.sub(point, vertexA), normal) / Vec.dotProduct(direction, normal)
     Vec.add vertexA, Vec.scale(direction, distance)
+
+  coalesceVertices: (vertices) ->
+    coalesced = []
+    for pair in Utils.sequentialPairs vertices
+      coalesced.push pair[0] unless Vec.distanceSquared(pair...) < 0.001
+    coalesced

 class Edge
   constructor: (@deepest, @from, @to) ->


TeamPlans: update plans to include maxInstance limit

--- workers/social/lib/social/models/computeproviders/teamplans.coffee
+++ workers/social/lib/social/models/computeproviders/teamplans.coffee
@@ -17,6 +17,7 @@
     validFor           : 0  # no expire date
     instancePerMember  : 1  # allows one instance per member
     allowedInstances   : [ 't2.micro' ]
+    maxInstance        : 1  # maximum instance count for this group (total)
     storagePerInstance : 5  # means 5GB storage for this plan in total (max).
                             # 1 member x 1 instancePerMember = 1 instance
                             # 5GB per instance x 1 instances = 5GB in total
@@ -28,6 +29,7 @@
     validFor           : 30 # in days (1 month)
     instancePerMember  : 1  # allows one instance per member
     allowedInstances   : [ 't2.micro' ]
+    maxInstance        : 10 # maximum instance count for this group (total)
     storagePerInstance : 5  # means 25GB storage for this plan in total (max).
                             # 5 member x 1 instancePerMember = 5 instances
                             # 5GB per instance x 5 instances = 25GB in total
@@ -39,6 +41,7 @@
     validFor           : 30 # in days (1 month)
     instancePerMember  : 2  # allows two instances per member
     allowedInstances   : [ 't2.micro', 't2.small' ]
+    maxInstance        : 20 # maximum instance count for this group (total)
     storagePerInstance : 10 # means 100GB storage for this plan in total (max).
                             # 5 member x 2 instancePerMember   = 10 instances
                             # 10GB per instance x 10 instances = 100GB in total
@@ -52,6 +55,7 @@
       't2.micro', 't2.small', 't2.medium', 't2.large'
       'm3.medium', 'm3.large', 'c3.large', 'c3.xlarge'
     ]
+    maxInstance        : 500
     storagePerInstance : 20
     restrictions       : BASIC_RESTRICTIONS

@@ -60,5 +64,6 @@
     validFor           : 0  # no expire date
     instancePerMember  : 90 # enough to run koding
     allowedInstances   : [] # all types of instances are allowed
+    maxInstance        : 9000
     storagePerInstance : 1000
     restrictions       : {}


Align object vars in Gruntfile

Align object vars in Gruntfile


Spacing

Spacing

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -22,7 +22,7 @@
         files: [{
           expand: true               # Enable dynamic expansion.
           cwd: 'src/less/skin'       # Src matches are relative to this path.
-          src: ['**/style-*.less']  # Actual pattern(s) to match.
+          src: ['**/style-*.less']   # Actual pattern(s) to match.
           dest: 'src/css/'           # Destination path prefix.
           ext: '.css'                # Dest filepaths will have this extension.
         }]


Remove mochaTest task from Gruntfile

Remove mochaTest task from Gruntfile

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -36,15 +36,6 @@
         src: ['**/*.coffee']
         dest: 'dist/test/'
         ext: '.js'
-
-    mochaTest:
-      test:
-        options:
-          # require: 'coffee-script/register'
-          reporter: 'spec'
-        src: [
-          'dist/test/**/*.js'
-        ]

     copy:
       dist:


Azure Test

Azure Test

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -220,10 +220,7 @@
 				separator: ';'
 				sourceMap: true
 			dist:
-				src: [
-					'.temp/scripts/*.js'
-					'.temp/scripts/**/*.js'
-				]
+				src: ['.temp/scripts/*.js', '.temp/scripts/**/*.js']
 				dest: '.temp/scripts/scripts.js'

 		# Run tasks when monitored files change


Update release task

Update release task

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -73,6 +73,13 @@
   # Tasks
   #

+  # Load tasks from plugins
+  grunt.loadNpmTasks "grunt-contrib-jshint"
+  grunt.loadNpmTasks "grunt-contrib-concat"
+  grunt.loadNpmTasks "grunt-contrib-uglify"
+  grunt.loadNpmTasks "grunt-bumpx"
+  grunt.loadNpmTasks "grunt-s3"
+
   # Task to tag a version in git
   grunt.registerTask "git-tag", "Tags a release in git", ->
     done = this.async()
@@ -82,13 +89,6 @@
       console.log("Error running git tag: " + error) if error?
       done(!error?)

-  # Load tasks from plugins
-  grunt.loadNpmTasks "grunt-contrib-jshint"
-  grunt.loadNpmTasks "grunt-contrib-concat"
-  grunt.loadNpmTasks "grunt-contrib-uglify"
-  grunt.loadNpmTasks "grunt-bumpx"
-  grunt.loadNpmTasks "grunt-s3"
-
   # Release meta-task
   grunt.registerTask "release", ["jshint", "uglify", "git-tag", "s3"]


change default task to build

change default task to build

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -197,7 +197,7 @@
   ]

   grunt.registerTask 'default', [
-    'test'
+    'build'
   ]

   grunt.registerTask 'ci', [


Add test task

Add test task

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -80,3 +80,4 @@

   grunt.registerTask 'build', ['concurrent', 'uglify', 'jasmine:all:build']
   grunt.registerTask 'default', ['connect', 'build', 'open', 'watch']
+  grunt.registerTask 'test', ['jasmine']


Remove x of y info for now, until we can make it prettier.

--- jquery.immybox.coffee
+++ jquery.immybox.coffee
@@ -145,9 +145,9 @@
       truncatedChoices = filteredChoices[0...@options.maxResults]
       difference = filteredChoices.length - truncatedChoices.length
       results = truncatedChoices.map (choice) -> "
#{esc choice.text}
"
-      info = if difference > 0
-        "
showing #{addCommas truncatedChoices.length} of #{addCommas filteredChoices.length}
"
-      else if results.length is 0
+      # info = if difference > 0
+      #   "
showing #{addCommas truncatedChoices.length} of #{addCommas filteredChoices.length}
"
+      info = if results.length is 0
         "
no matches
"
       else
         ''


Logout trigger TEMPORARY

Logout trigger TEMPORARY

--- public/scripts/backbone-schedule/views/main-nav.coffee
+++ public/scripts/backbone-schedule/views/main-nav.coffee
@@ -46,7 +46,8 @@
       Shark.session.login()

     showUserMenu: ->
-      @$el.find('#user-menu').toggleClass('open');
+      Shark.session.logout()
+      #@$el.find('#user-menu').toggleClass('open');

   # Whatever is returned here will be usable by other modules


only change zoombar on first click

only change zoombar on first click

Former-commit-id: 42c370dd242d694d32809a992e6c5854b4db9509 [formerly 973096e4bcace1fb0a7251e4167ddb1e3b90363b]
Former-commit-id: ec3dca54da77a94b19778a4f205b98cf6dc6c5d8

--- js/msa/zoombar.coffee
+++ js/msa/zoombar.coffee
@@ -22,8 +22,13 @@
       #zoomSlider.addEventListener "change", (evt) =>
       #  @_reDraw()

+      zoomSlider.addEventListener "click", (evt) =>
+        @firstClick = true
+        @_reDraw()
+
       zoomSlider.addEventListener "mousemove", (evt) =>
-        if @lastValue isnt @zoomSlider.value
+        # only update after first click and on value change
+        if @lastValue isnt @zoomSlider.value and @firstClick?
           @lastValue = @zoomSlider.value
           unless @locked
             @locked = true


aggregations: description updated

aggregations: description updated

--- _assets/javascripts/talks/index.js.coffee
+++ _assets/javascripts/talks/index.js.coffee
@@ -12,5 +12,5 @@
   id: 'aggregations',
   title: 'MongoDB hierarchical aggregations',
   name: 'MongoDB hierarchical aggregations',
-  description: 'build multi-scale time-based statistic and aggregations with MongoDB map-reduce funcionality'
+  description: 'build multi-scale time-based aggregations with MongoDB map-reduce'
 }]


add app id to configuration

add app id to configuration

--- tests/test_application.coffee
+++ tests/test_application.coffee
@@ -17,6 +17,7 @@
   }

   Configuration: {
+    appId: 'CustomerApp'
     eventSourcing: {
       snapshotting: {
         frequency: 2


Social: remove round robin code from application

--- workers/social/lib/social/models/socialapi/requests.coffee
+++ workers/social/lib/social/models/socialapi/requests.coffee
@@ -4,37 +4,13 @@
 request        = require 'request'
 _ = require "underscore"

-SOCIAL_API_FALLBACK_URL = KONFIG.socialapi.fallbackUrl
-SOCIAL_API_URLS = []
-
-fetchApiURLs = (callback)->
-  { url, port } = KONFIG.kontrold.api
-  url = "#{ url }:#{ port }/workers/url/socialapiapi?all"
-  request url, {json:true}, (error, response, body) ->
-    if error or response.statusCode isnt 200 or not body
-      return callback {message: "Social Api is not reachable"}
-
-    return callback null, body
-
 getNextApiURL = (callback)->
-  unless SOCIAL_API_URLS.length > 0
-    fetchApiURLs (err, urls)->
-      return callback err if err
-      unless urls?.length > 0
-        console.warn "serving fallbackUrl", SOCIAL_API_FALLBACK_URL
-        return callback null, SOCIAL_API_FALLBACK_URL
-      SOCIAL_API_URLS = urls
-      nextapi = _.sample urls
-      return callback null, nextapi
-  else
-    nextapi = _.sample SOCIAL_API_URLS
-    return callback null, nextapi
+  return callback null, KONFIG.socialapi.proxyUrl

 wrapCallback = (callback)->
   (err, response, body) ->
     if err
       if err.code is "ECONNREFUSED"
-        SOCIAL_API_URLS = []
         return callback {message: "Social API is currently under maintenance"}
       return callback err


Refactored GWTab.

Refactored GWTab.

--- cakes.coffee
+++ cakes.coffee
@@ -95,7 +95,7 @@
 		mocha.it message, callback
 ###

-commandNest = (command, message, callback, options)->
+commandNest = (command, message, callback, options)->  # nest commands inside a Describe so mixed describe/its will line up on spec output.
 	if 'label' in options
 		label = '◦'
 		if 'labelcolor' in options
@@ -122,61 +122,30 @@

 	commandNest command, message, callback, options

-createGiven = (options)->
+createGWTab = (label, options)->  # Creates Given, When, Then, and, but commands
 	return (message, callback)->
-		if 'label' in options
-			label = 'Given: '
-			if 'labelcolor' in options
-				label = label.yellow
+		if label && 'labelcolor' in options
+			label = labelColor label, options
+
 		itDescribe label, message, callback, options

-
-
-exports.Given = createGiven(['label', 'labelcolor'])
-
-createWhen = (options)->
-	return (message, callback)->
-		if 'label' in options
-			label = ' When: '
-			if 'labelcolor' in options
-				label = label.yellow
-
-		itDescribe label, message, callback, options
-
-exports.When = createWhen(['label', 'labelcolor'])
-
-createThen = (options)->
-	return (message, callback)->
-		if 'label' in options
-			label = ' Then: '
-			if 'labelcolor' in options
-				label = label.yellow
-
-		itDescribe label, message, callback, options
-
-exports.Then = createThen(['label', 'labelcolor'])
-
-createAnd = (options)->
-	return (message, callback)->
-		if 'label' in options
-			label = '  And: '
-			if 'labelcolor' in options
-				label = label.grey
-
-		itDescribe label, message, callback, options
-
-exports.And = createAnd(['label', 'labelcolor', 'dark'])
-
-createBut = (options)->
-	return (message, callback)->
-		if 'label' in options
-			label = '  But: '
-			if 'labelcolor' in options
-				label = label.grey
-
-		itDescribe label, message, callback, options
-
-exports.But = createBut(['label', 'labelcolor', 'dark'])
+labelColor = (label, options)->
+	if 'dark' in options
+		label = label.grey
+	else
+		label = label.yellow
+	return label
+
+
+exports.Given = createGWTab('Given: ', ['label', 'labelcolor'])
+
+exports.When = createGWTab(' When: ', ['label', 'labelcolor'])
+
+exports.Then = createGWTab(' Then: ', ['label', 'labelcolor'])
+
+exports.And = createGWTab('  And: ', ['label', 'labelcolor', 'dark'])
+
+exports.But = createGWTab('  But: ', ['label', 'labelcolor', 'dark'])

 ### Start of Spec/Describe ###


update atom config

update atom config

--- atom/config.cson
+++ atom/config.cson
@@ -16,6 +16,7 @@
     invisibles: {}
     zoomFontWhenCtrlScrolling: true
     showInvisibles: true
+    showIndentGuide: true
   "coffee-refactor": {}
   react: {}
   minimap:
@@ -23,6 +24,7 @@
       "git-diff": true
       "find-and-replace": true
       selection: true
+      pigments: true
   "one-dark-ui": {}
   linter: {}
   "vim-mode":
@@ -41,3 +43,5 @@
     ]
   "vim-surround": {}
   jumpy: {}
+  jshint:
+    supportLintingJsx: true


Add missing keymap comments

Add missing keymap comments

--- atom/keymap.cson
+++ atom/keymap.cson
@@ -1,5 +1,6 @@
 # Your keymap

+# Atom window
 '.platform-darwin':

   # Disable cmd-o because it always gets in the way of typing cmd-i
@@ -9,6 +10,7 @@
   'cmd-\\': 'unset!'
   'ctrl-alt-cmd-\\': 'tree-view:toggle'

+# Atom workspace
 '.platform-darwin atom-workspace':

   # Create new files using advanced-open-file


Add tab-switcher hotkeys

Add tab-switcher hotkeys

--- atom/keymap.cson
+++ atom/keymap.cson
@@ -1,14 +1,20 @@
 '.platform-win32, .platform-linux':
-  'alt-\\': 'unset!' # 'tree-view:toggle-focus'
-  'ctrl-|': 'unset!'
+  'alt-\\': 'unset!' # tree-view:toggle-focus default
+  'ctrl-|': 'unset!' # tree-view:reveal-active-file default
   'ctrl-0': 'tree-view:reveal-active-file'

 'atom-workspace atom-text-editor:not([mini])':
-  'ctrl-up': 'unset!'
+  'ctrl-up': 'unset!' # editor:move-line-up default
   'ctrl-shift-up': 'editor:move-line-up'
-  'ctrl-down': 'unset!'
+  'ctrl-down': 'unset!' # editor:move-line-down default
   'ctrl-shift-down': 'editor:move-line-down'

 'atom-text-editor:not([mini])':
-  'f5': 'unset!'
+  'f5': 'unset!' # sort-lines:sort default
   'f9': 'sort-lines:sort'
+
+'atom-workspace':
+  'alt-]': 'unset!' # tab-switcher:next default
+  'ctrl-tab': 'tab-switcher:next'
+  'alt-[': 'unset!' # tab-switcher:previous default
+  'ctrl-shift-tab': 'tab-switcher:previous'


get rid of confusing regex

get rid of confusing regex

--- services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager.coffee
+++ services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager.coffee
@@ -162,7 +162,7 @@
 			cursorPosition = @editor.getCursorPosition()
 			end = change.end
 			{lineUpToCursor, commandFragment} = Helpers.getContext(@editor, end)
-			if lineUpToCursor.match(/.*((?![\\]).|^)%.*/)
+			if (i = lineUpToCursor.indexOf('%') > -1 and lineUpToCursor[i-1] != '\\')
 				return
 			lastCharIsBackslash = lineUpToCursor.slice(-1) == "\\"
 			lastTwoChars = lineUpToCursor.slice(-2)


Use existential operator to check existence

--- services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager.coffee
+++ services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager.coffee
@@ -183,11 +183,11 @@
 											editor.session.remove(right);
 											# trim the completion text to just the command, without braces
 											# example: '\cite{}' -> '\cite'
-											if matchData.snippet
+											if matchData.snippet?
 												matchData.snippet = matchData.snippet.replace(/[{\[].*[}\]]/, '')
-											if matchData.caption
+											if matchData.caption?
 												matchData.caption = matchData.caption.replace(/[{\[].*[}\]]/, '')
-											if matchData.value
+											if matchData.value?
 												matchData.value = matchData.value.replace(/[{\[].*[}\]]/, '')
 								# finally, insert the match
 								if matchData.snippet


Reduce chromedriver waiting time to 500ms

--- spec/integration/helpers/start-atom.coffee
+++ spec/integration/helpers/start-atom.coffee
@@ -114,7 +114,7 @@
       chromedriver.stderr.on "close", ->
         resolve(errorCode)

-  waits(1000)
+  waits(500)

   waitsFor("webdriver to finish", (done) ->
     finish = once ->


Revert "FIX: fix default selected task for tutors"

This reverts commit 89bd10ec1312099ba166834a44b0262a337ba8fd.

--- src/app/units/states/tasks/inbox/directives/staff-task-list/staff-task-list.coffee
+++ src/app/units/states/tasks/inbox/directives/staff-task-list/staff-task-list.coffee
@@ -37,13 +37,6 @@
       filteredTasks = $filter('tasksInTutorials')(filteredTasks, $scope.filters.tutorials)
       filteredTasks = $filter('tasksWithStudentName')(filteredTasks, $scope.filters.studentName)
       $scope.filteredTasks = filteredTasks
-
-      # Fix selected task.
-      if $scope.taskData.selectedTask && !_.includes(filteredTasks, $scope.taskData.selectedTask)
-        $scope.setSelectedTask(null)
-      if filteredTasks && filteredTasks.length && !$scope.taskData.selectedTask
-        $scope.setSelectedTask(filteredTasks[0])
-
     # Let's call having a source of tasksForDefinition plus having a task definition
     # auto-selected with the search options open task def mode -- i.e., the mode
     # for selecting tasks by task definitions


Client: Activity: Updated leaveChannel method to set route to default group after leaving private messages

--- client/activity/lib/components/channelthreadpane/index.coffee
+++ client/activity/lib/components/channelthreadpane/index.coffee
@@ -8,7 +8,7 @@
 ImmutableRenderMixin = require 'react-immutable-render-mixin'
 PublicChatPane       = require 'activity/components/publicchatpane'
 ChannelDropContainer = require 'activity/components/channeldropcontainer'
-
+getGroup             = require 'app/util/getGroup'

 module.exports = class ChannelThreadPane extends React.Component

@@ -43,7 +43,17 @@
   invitePeople: -> @refs.pane.onInviteOthers()

-  leaveChannel: -> actions.channel.unfollowChannel @channel 'id'
+  leaveChannel: ->
+
+    channelId = @channel 'id'
+
+    if @channel('typeConstant') is "privatemessage"
+      actions.channel.leavePrivateChannel channelId
+        .then ->
+          channelName = getGroup().slug
+          kd.singletons.router.handleRoute "/Channels/#{channelName}"
+    else
+      actions.channel.unfollowChannel channelId

   renderHeader: ->


teams: activity: performance improvement? cc/ @usirin

--- client/activity/lib/components/emojiselectoritem/index.coffee
+++ client/activity/lib/components/emojiselectoritem/index.coffee
@@ -1,11 +1,14 @@
-kd              = require 'kd'
-React           = require 'kd-react'
-immutable       = require 'immutable'
-classnames      = require 'classnames'
-formatEmojiName = require 'activity/util/formatEmojiName'
-DropboxItem     = require 'activity/components/dropboxitem'
+kd                   = require 'kd'
+React                = require 'kd-react'
+immutable            = require 'immutable'
+classnames           = require 'classnames'
+formatEmojiName      = require 'activity/util/formatEmojiName'
+DropboxItem          = require 'activity/components/dropboxitem'
+ImmutableRenderMixin = require 'react-immutable-render-mixin'

 module.exports = class EmojiSelectorItem extends React.Component
+
+  @include [ ImmutableRenderMixin ]

   @defaultProps =
     item         : immutable.Map()


payment: fix for downgrading to free plan when current plan is expired

--- client/app/lib/providers/environmentsmachinestatemodal.coffee
+++ client/app/lib/providers/environmentsmachinestatemodal.coffee
@@ -311,15 +311,18 @@

           @showBusy "Deleting your VM(s)..."
           ComputeHelpers.destroyExistingMachines (err)=>
-            kd.utils.wait 5000, =>
-              @buildExpiredView subscription, "downgrade"
+            @buildExpiredView subscription, "downgrade"
+          , yes

         else

           @showBusy "Downgrading..."
-          @downgradePlan (err)=> if err? \
-            then @buildExpiredView subscription, "destroy-vms"
-            else @buildInitial()
+          @downgradePlan (err) =>
+            if err?
+              @buildExpiredView subscription, "destroy-vms"
+            else
+              @_busy = no
+              @buildInitial()

     @container.addSubView new KDCustomHTMLView
       cssClass : 'expired-message'


Fix spec

Fix spec

--- spec/language-haskell-spec.coffee
+++ spec/language-haskell-spec.coffee
@@ -309,16 +309,16 @@
       ]
     it "understands module declarations with export lists", ->
       g = grammarExpect grammar, 'module Module (export1 (..), export2 (Something)) where'
-      g.toHaveTokens [['module', ' ', 'Module', ' ', '(', 'export1', ' (' , '..', ')',
-                       ',', ' ', 'export2', ' (', 'Something', ')', ')', ' ', 'where']]
+      g.toHaveTokens [['module', ' ', 'Module', ' ', '(', 'export1', ' ', '(' , '..', ')',
+                       ',', ' ', 'export2', ' ', '(', 'Something', ')', ')', ' ', 'where']]
       g.toHaveScopes [['source.haskell', 'meta.declaration.module.haskell']]
       g.tokenToHaveScopes [
         2: ['support.other.module.haskell']
         5: ['meta.declaration.exports.haskell', 'entity.name.function.haskell']
-        7: ['meta.declaration.exports.haskell', 'meta.other.constructor-list.haskell',
+        8: ['meta.declaration.exports.haskell', 'meta.other.constructor-list.haskell',
              'keyword.operator.wildcard.haskell']
-        11: ['meta.declaration.exports.haskell', 'entity.name.function.haskell']
-        13: ['meta.declaration.exports.haskell', 'meta.other.constructor-list.haskell',
+        12: ['meta.declaration.exports.haskell', 'entity.name.function.haskell']
+        15: ['meta.declaration.exports.haskell', 'meta.other.constructor-list.haskell',
               'entity.name.tag.haskell']
       ]
   describe "regression test for comments after module name in imports", ->


Adding more error control to sprint creation

--- app/coffee/modules/backlog/lightboxes.coffee
+++ app/coffee/modules/backlog/lightboxes.coffee
@@ -172,9 +172,13 @@
             if not form.validate()
                 return

-            $repo.create("milestones", $scope.sprint).then (data) ->
+            promise = $repo.create("milestones", $scope.sprint)
+            promise.then (data) ->
                 $el.addClass("hidden")
                 $rootscope.$broadcast("sprintform:create:success", data)
+
+            promise.then null, (data) ->
+                form.setErrors(data)

         $scope.$on "sprintform:create", ->
             $el.removeClass("hidden")


Add Locale Selector

Add Locale Selector

--- TheOpenCMS/Engines/TheOpenCMS/app_base/app/assets/javascripts/app_base/frontend/select_locale.coffee
+++ TheOpenCMS/Engines/TheOpenCMS/app_base/app/assets/javascripts/app_base/frontend/select_locale.coffee
@@ -1,6 +1,6 @@
 @SelectLocale = do ->
   init: ->
-    @init ||= do ->
+    @inited ||= do ->
       $(document).on 'change', '.js-select_locale', (e) ->
         select = $(e.currentTarget)
         locale = select.val()


fix https://github.com/sagemathinc/cocalc/issues/2464 -- jupyter close and halt

--- src/smc-webapp/jupyter/top-menubar.cjsx
+++ src/smc-webapp/jupyter/top-menubar.cjsx
@@ -86,15 +86,14 @@
             rename = '<' + rename

         close_and_halt = 'close and halt'
-        if not @props.has_unsaved_changes or @props.read_only
+        if @props.read_only
             close_and_halt = '<' + close_and_halt

         names = [
-                'new notebook', 'open file', '', \
+                'new notebook', 'open file', close_and_halt, '', \
                 'duplicate notebook', rename, save, 'time travel', '', \
                 'print preview', '<Download as...', '>nbconvert ipynb',  script_entry, '>nbconvert html', '>nbconvert markdown', '>nbconvert rst', '>nbconvert tex', '>nbconvert pdf',  '>nbconvert sagews', '', '>nbconvert slides', '>nbconvert asciidoc', '', \
-                trust, '', \
-                close_and_halt]
+                trust]
         if @props.fullscreen != 'kiosk'
             names.push('')
             names.push('switch to classical notebook')


fix(tests): Enable disabled tests

fix(tests): Enable disabled tests

--- src/spec/integration/export.spec.coffee
+++ src/spec/integration/export.spec.coffee
@@ -72,7 +72,7 @@
   , 60000 # 60sec

-  xit 'should inform about a bad header in the template', (done) ->
+  it 'should inform about a bad header in the template', (done) ->
     template =
       '''
       productType,name,name
@@ -86,7 +86,7 @@
       expect(err[1]).toBe "You need either the column 'variantId' or 'sku' to identify your variants!"
       done()

-  xit 'should inform that there are no products', (done) ->
+  it 'should inform that there are no products', (done) ->
     template =
       '''
       productType,name,variantId
@@ -107,7 +107,7 @@
       done()
     .catch (err) -> done _.prettify(err)

-  xit 'should export based on minimum template', (done) ->
+  it 'should export based on minimum template', (done) ->
     template =
       '''
       productType,name,variantId
@@ -129,7 +129,7 @@
       done()
     .catch (err) -> done _.prettify(err)

-  xit 'should export labels of lenum and set of lenum', (done) ->
+  it 'should export labels of lenum and set of lenum', (done) ->
     template =
     '''
       productType,name,variantId,attr-lenum-n.en,attr-set-lenum-n.en
@@ -152,7 +152,7 @@
     .catch (err) -> done _.prettify(err)

-  xit 'should do a full export', (done) ->
+  it 'should do a full export', (done) ->
     tempDir = tmp.dirSync({ unsafeCleanup: true })
     outputLocation = path.join tempDir.name, 'output.zip'
     expectedHeader = '_published,_hasStagedChanges,productType,variantId,id,sku,prices,tax,categories,images,name.en,description.en,slug.en,metaTitle.en,metaDescription.en,metaKeywords.en,searchKeywords.en,attr-text-n,attr-ltext-n.en,attr-enum-n,attr-lenum-n,attr-number-n,attr-boolean-n,attr-money-n,attr-date-n,attr-time-n,attr-datetime-n,attr-ref-product-n,attr-ref-product-type-n,attr-ref-channel-n,attr-ref-state-n,attr-ref-zone-n,attr-ref-shipping-method-n,attr-ref-category-n,attr-ref-review-n,attr-ref-key-value-n,attr-set-text-n,attr-set-ltext-n.en,attr-set-enum-n,attr-set-lenum-n,attr-set-number-n,attr-set-boolean-n,attr-set-money-n,attr-set-date-n,attr-set-time-n,attr-set-datetime-n,attr-set-ref-product-n,attr-set-ref-product-type-n,attr-set-ref-channel-n,attr-set-ref-state-n,attr-set-ref-zone-n,attr-set-ref-shipping-method-n,attr-set-ref-category-n,attr-set-ref-review-n,attr-set-ref-key-value-n,attr-text-u,attr-ltext-u.en,attr-enum-u,attr-lenum-u,attr-number-u,attr-boolean-u,attr-money-u,attr-date-u,attr-time-u,attr-datetime-u,attr-ref-product-u,attr-ref-product-type-u,attr-ref-channel-u,attr-ref-state-u,attr-ref-zone-u,attr-ref-shipping-method-u,attr-ref-category-u,attr-ref-review-u,attr-ref-key-value-u,attr-set-text-u,attr-set-ltext-u.en,attr-set-enum-u,attr-set-lenum-u,attr-set-number-u,attr-set-boolean-u,attr-set-money-u,attr-set-date-u,attr-set-time-u,attr-set-datetime-u,attr-set-ref-product-u,attr-set-ref-product-type-u,attr-set-ref-channel-u,attr-set-ref-state-u,attr-set-ref-zone-u,attr-set-ref-shipping-method-u,attr-set-ref-category-u,attr-set-ref-review-u,attr-set-ref-key-value-u,attr-text-cu,attr-ltext-cu.en,attr-enum-cu,attr-lenum-cu,attr-number-cu,attr-boolean-cu,attr-money-cu,attr-date-cu,attr-time-cu,attr-datetime-cu,attr-ref-product-cu,attr-ref-product-type-cu,attr-ref-channel-cu,attr-ref-state-cu,attr-ref-zone-cu,attr-ref-shipping-method-cu,attr-ref-category-cu,attr-ref-review-cu,attr-ref-key-value-cu,attr-set-text-cu,attr-set-ltext-cu.en,attr-set-enum-cu,attr-set-lenum-cu,attr-set-number-cu,attr-set-boolean-cu,attr-set-money-cu,attr-set-date-cu,attr-set-time-cu,attr-set-datetime-cu,attr-set-ref-product-cu,attr-set-ref-product-type-cu,attr-set-ref-channel-cu,attr-set-ref-state-cu,attr-set-ref-zone-cu,attr-set-ref-shipping-method-cu,attr-set-ref-category-cu,attr-set-ref-review-cu,attr-set-ref-key-value-cu,attr-text-sfa,attr-ltext-sfa.en,attr-enum-sfa,attr-lenum-sfa,attr-number-sfa,attr-boolean-sfa,attr-money-sfa,attr-date-sfa,attr-time-sfa,attr-datetime-sfa,attr-ref-product-sfa,attr-ref-product-type-sfa,attr-ref-channel-sfa,attr-ref-state-sfa,attr-ref-zone-sfa,attr-ref-shipping-method-sfa,attr-ref-category-sfa,attr-ref-review-sfa,attr-ref-key-value-sfa,attr-set-text-sfa,attr-set-ltext-sfa.en,attr-set-enum-sfa,attr-set-lenum-sfa,attr-set-number-sfa,attr-set-boolean-sfa,attr-set-money-sfa,attr-set-date-sfa,attr-set-time-sfa,attr-set-datetime-sfa,attr-set-ref-product-sfa,attr-set-ref-product-type-sfa,attr-set-ref-channel-sfa,attr-set-ref-state-sfa,attr-set-ref-zone-sfa,attr-set-ref-shipping-method-sfa,attr-set-ref-category-sfa,attr-set-ref-review-sfa,attr-set-ref-key-value-sfa'
@@ -207,7 +207,7 @@
     .finally ->
       tempDir.removeCallback()

-  xit 'should export data in different encoding', (done) ->
+  it 'should export data in different encoding', (done) ->
     encoding = 'win1250'
     template =
     '''
@@ -240,7 +240,7 @@
       done()
     .catch (err) -> done _.prettify(err)

-  xit 'should export product with money set attribute', (done) ->
+  it 'should export product with money set attribute', (done) ->
     testProductType = require '../../data/moneySetAttributeProductType'
     testProduct = require '../../data/moneySetAttributeProduct'
     outputLocation = '/tmp/output.csv'


test(searchKeywords): add integration test for updating search keywords

--- src/spec/integration/import.spec.coffee
+++ src/spec/integration/import.spec.coffee
@@ -486,6 +486,52 @@
         done()
       .catch (err) -> done _.prettify(err)

+    it 'should do a partial update of search keywords', (done) ->
+      csv =
+        """
+        productType,name.en,slug.en,searchKeywords.en,searchKeywords.fr,searchKeywords.de
+        #{@productType.id},#{@newProductName},#{@newProductSlug},new;search;keywords,nouvelle;trouve,deutsche;kartoffel
+        """
+      @importer.import(csv)
+      .then (result) =>
+        expect(_.size result).toBe 1
+        expect(result[0]).toBe '[row 2] New product created.'
+        csv =
+          """
+          productType,slug.en,searchKeywords.en,searchKeywords.fr
+          #{@productType.id},#{@newProductSlug},newNew;search;keywords,nouvelleNew;trouveNew
+          """
+        im = createImporter()
+        im.matchBy = 'slug'
+        im.import(csv)
+      .then (result) =>
+        expect(_.size result).toBe 1
+        expect(result[0]).toBe '[row 2] Product updated.'
+        @client.productProjections.staged(true).where("name (en = \"#{@newProductName}\")").fetch()
+      .then (result) =>
+        expect(result.body.results[0].searchKeywords).toEqual
+          "en": [
+            {
+              "text": "newNew"
+            },
+            {
+              "text": "search"
+            },
+            {
+              "text": "keywords"
+            }
+          ],
+          "fr": [
+            {
+              "text": "nouvelleNew"
+            },
+            {
+              "text": "trouveNew"
+            }
+          ]
+        done()
+      .catch (err) -> done _.prettify(err)
+
     it 'should do a partial update of localized attributes', (done) ->
       csv =
         """


port get and set body.position correctly

--- canvas/game.coffee
+++ canvas/game.coffee
@@ -152,10 +152,10 @@
     constructor: (pos, @flavor_index, @sprite, @space) ->
       super
       body = new cp.Body(10, 100000)
-      body.position = pos
+      body.setPos pos
       @shape = new cp.CircleShape(body, atom_radius, new Vec2d())
-      @shape.u = 0.5
-      @shape.e = 0.05
+      @shape.setFriction 0.5
+      @shape.setElasticity 0.05
       @shape.collision_type = Collision.Atom
       @space.addBody(body)
       @space.addShape(@shape)
@@ -230,10 +230,10 @@
     constructor: (pos, @sprite, @space, @timeout) ->
       super
       body = new cp.Body(50, 10)
-      body.position = pos
+      body.setPos pos
       @shape = new cp.CircleShape(body, Bomb.radius, new Vec2d())
-      @shape.u = 0.7
-      @shape.e = 0.02
+      @shape.setFriction 0.7
+      @shape.setElasticity 0.02
       @shape.collision_type = Collision.Default
       @space.addBody(body)
       @space.addShape(@shape)
@@ -254,10 +254,10 @@
     constructor: (pos, @sprite, @space) ->
       super
       body = new cp.Body(70, 100000)
-      body.position = pos
+      body.setPos pos
       @shape = new cp.CircleShape(body, Rock.radius, new Vec2d())
-      @shape.u = 0.9
-      @shape.e = 0.01
+      @shape.setFriction 0.9
+      @shape.setElasticity 0.01
       @shape.collision_type = Collision.Default
       @space.addBody(body)
       @space.addShape(@shape)
@@ -395,8 +395,8 @@
         @computeDrops(dt)

       # check if we died
-      ratio = @atoms.length / (@ceiling.body.position.y - @size.y / 2)
-      if ratio > @lose_ratio or @ceiling.body.position.y < @man_size.y
+      ratio = @atoms.length / (@ceiling.body.p.y - @size.y / 2)
+      if ratio > @lose_ratio or @ceiling.body.p.y < @man_size.y
         @lose()

       # process bombs
@@ -405,20 +405,19 @@
         if bomb.timeout <= 0
           # physics explosion
           # loop over every object in the space and apply an impulse
-          for shape in @space.shapes
-            vector = shape.body.position.minus(bomb.shape.body.position)
+          for body in @space.bodies
+            vector = body.p.minus(bomb.shape.body.p)
             dist = vector.length()
             direction = vector.normalized()
             power = 6000
             damp = 1 - dist / 800
-            shape.body.applyImpulse(direction.scale(power).scale(damp))
+            body.applyImpulse(direction.scaled(power * damp), new Vec2d(0,0))

           # explosion animation
           sprite = new Engine.Sprite("bombsplode", batch:@game.batch, zorder:@game.group_fg)
-          sprite.pos = @pos.plus(bomb.shape.body.position)
-          do (sprite) =>
-            removeBombSprite = => sprite.delete()
-          sprite.on("animation_end", removeBombSprite)
+          sprite.pos = @pos.plus(bomb.shape.body.p)
+          removeBombSprite = null
+          sprite.on "animation_end", do (sprite) => => sprite.delete()
           @removeBomb(bomb)

           @playSfx("explode")
@@ -477,8 +476,8 @@
         body = new cp.Body(Infinity, Infinity)
         body.nodeIdleTime = Infinity
         shape = new cp.SegmentShape(body, p1, p2, r)
-        shape.u = 0.99
-        shape.e = 0.0
+        shape.setFriction 0.99
+        shape.setElasticity 0.0
         shape.collision_type = Collision.Default
         @space.addStaticShape(shape)
       return
@@ -486,7 +485,7 @@
     initCeiling: =>
       # physics for ceiling
       body = new cp.Body(10000, 100000)
-      body.position = new Vec2d(@size.x / 2, @size.y * 1.5)
+      body.setPos new Vec2d(@size.x / 2, @size.y * 1.5)
       @ceiling = new cp.BoxShape(body, @size.x, @size.y)
       @ceiling.collision_type = Collision.Default
       @space.addShape(@ceiling)
@@ -506,15 +505,15 @@
         adjust = 0
       target_y = @size.y * 1.5 + adjust

-      direction = sign(target_y - @ceiling.body.position.y)
+      direction = sign(target_y - @ceiling.body.p.y)
       amount = @max_ceiling_delta * dt
-      new_y = @ceiling.body.position.y + amount * direction
+      new_y = @ceiling.body.p.y + amount * direction
       new_sign = sign(target_y - new_y)
       if direction is -new_sign
         # close enough to just set
-        @ceiling.body.position.y = target_y
+        @ceiling.body.setPos new Vec2d(@ceiling.body.p.x, target_y)
       else
-        @ceiling.body.position.y = new_y
+        @ceiling.body.setPos new Vec2d(@ceiling.body.p.x, new_y)

     initMan: (pos, vel) =>
       if not pos?
@@ -523,12 +522,12 @@
         vel = new Vec2d(0, 0)
       # physics for man
       shape = new cp.BoxShape(new cp.Body(20, 10000000), @man_size.x, @man_size.y)
-      shape.body.position = pos
+      shape.body.setPos pos
       shape.body.velocity = vel
       shape.body.w_limit = 0
       @man_angle = shape.body.a
-      shape.e = 0
-      shape.u = 3.0
+      shape.setElasticity 0
+      shape.setFriction 3.0
       shape.collision_type = Collision.Default
       @space.addBody(shape.body)
       @space.addShape(shape)
@@ -536,14 +535,14 @@

     computeArmPos: =>
-      @arm_pos = @man.body.position.minus(@man_size.scale(0.5)).plus(@arm_offset)
+      @arm_pos = @man.body.p.minus(@man_size.scaled(0.5)).plus(@arm_offset)
       @point_vector = (@mouse_pos.minus(@arm_pos)).normalized()
-      @point_start = @arm_pos.plus(@point_vector.scale(@arm_len))
+      @point_start = @arm_pos.plus(@point_vector.scaled(@arm_len))

     getDropPos: (size) =>
       return new Vec2d(
         Math.random() * (@size.x - size.x) + size.x / 2,
-        @ceiling.body.position.y - @size.y / 2 - size.y / 2,
+        @ceiling.body.p.y - @size.y / 2 - size.y / 2,
       )

@@ -633,7 +632,7 @@
       for btn, ctrl of @controls
         @control_state[ctrl] = @game.engine.buttonState(btn)

-      feet_start = @man.body.position.minus(@man_size.scale(0.5)).offset(1, -1)
+      feet_start = @man.body.p.minus(@man_size.scaled(0.5)).offset(1, -1)
       feet_end = feet_start.offset(@man_size.x - 2, -2)
       bb = new cp.BB(feet_start.x, feet_end.y, feet_end.x, feet_start.y)
       ground_shapes = []
@@ -652,17 +651,17 @@
       move_left = @control_state[Control.MoveLeft] and not @control_state[Control.MoveRight]
       move_right = @control_state[Control.MoveRight] and not @control_state[Control.MoveLeft]
       if move_left
-        if @man.body.velocity.x >= -max_speed and @man.body.position.x - @man_size.x / 2 - 5 > 0
+        if @man.body.velocity.x >= -max_speed and @man.body.p.x - @man_size.x / 2 - 5 > 0
           @man.body.applyImpulse(new Vec2d(-move_force, 0), new Vec2d(0, 0))
           if @man.body.velocity.x > -move_boost and @man.body.velocity.x < 0
             @man.body.velocity.x = -move_boost
       else if move_right
-        if @man.body.velocity.x <= max_speed and @man.body.position.x + @man_size.x / 2 + 3 < @size.x
+        if @man.body.velocity.x <= max_speed and @man.body.p.x + @man_size.x / 2 + 3 < @size.x
           @man.body.applyImpulse(new Vec2d(move_force, 0), new Vec2d(0, 0))
           if @man.body.velocity.x < move_boost and @man.body.velocity.x > 0
             @man.body.velocity.x = move_boost

-      negate = @mouse_pos.x < @man.body.position.x
+      negate = @mouse_pos.x < @man.body.p.x
       # jumping
       if grounded
         if move_left or move_right
@@ -721,12 +720,12 @@
           @claw_in_motion = true
           @sprite_claw.setVisible true
           body = new cp.Body(5, 1000000)
-          body.position = new Vec2d(@point_start)
+          body.setPos new Vec2d(@point_start)
           body.setAngle @point_vector.angle()
           body.velocity = @man.body.velocity + @point_vector * @claw_shoot_speed
           @claw = new cp.CircleShape(body, @claw_radius, new Vec2d())
-          @claw.u = 1
-          @claw.e = 0
+          @claw.setFriction 1
+          @claw.setElasticity 0
           @claw.collision_type = Collision.Claw
           @claw_joint = new cp.SlideJoint(@claw.body, @man.body, new Vec2d(0, 0), new Vec2d(0, 0), 0, @size.length())
           @claw_joint.max_bias = max_bias
@@ -737,7 +736,7 @@
           @playSfx('shoot_claw')

         if @sprite_claw.visible
-          claw_dist = @claw.body.position.minus(@man.body.position).length()
+          claw_dist = @claw.body.p.minus(@man.body.p).length()

         if @control_state[Control.FireMain] and @claw_in_motion
           if claw_dist < @min_claw_dist + 8
@@ -779,13 +778,13 @@

       if @ray_atom?
         # move the atom closer to the ray gun
-        vector = @point_start.minus(@ray_atom.shape.body.position)
-        delta = vector.normalized().scale(1000 * dt)
+        vector = @point_start.minus(@ray_atom.shape.body.p)
+        delta = vector.normalized().scaled(1000 * dt)
         if delta.length() > vector.length()
           # just move the atom to final location
-          @ray_atom.shape.body.position = @point_start
+          @ray_atom.shape.body.setPos @point_start
         else
-          @ray_atom.shape.body.position += delta
+          @ray_atom.shape.body.setPos @ray_atom.shape.body.p.plus(delta)

       if @equipped_gun is Control.SwitchToRay
         if (@control_state[Control.FireMain] and @let_go_of_fire_main) and @closest_atom? and not @ray_atom? and not @closest_atom.marked_for_deletion
@@ -856,10 +855,10 @@
       claw = arbiter.a
       shape = arbiter.b
       pos = new Vec2d(arbiter.contacts[0].p)
-      shape_anchor = pos.minus(shape.body.position)
-      claw_anchor = pos.minus(claw.body.position)
-      claw_delta = claw_anchor.normalized().scale(-(@claw_radius + 8))
-      @claw.body.position.add(claw_delta)
+      shape_anchor = pos.minus(shape.body.p)
+      claw_anchor = pos.minus(claw.body.p)
+      claw_delta = claw_anchor.normalized().scaled(-(@claw_radius + 8))
+      @claw.body.setPos @claw.body.p.plus(claw_delta)
       @claw_pins_to_add = [
         new cp.PinJoint(claw.body, shape.body, claw_anchor, shape_anchor),
         new cp.PinJoint(claw.body, shape.body, new Vec2d(0, 0), new Vec2d(0, 0)),
@@ -911,7 +910,7 @@
           if atom.marked_for_deletion
             return
           # http://stackoverflow.com/questions/1073336/circle-line-collision-detection
-          f = atom.shape.body.position.minus(@point_start)
+          f = atom.shape.body.p.minus(@point_start)
           if sign(f.x) isnt sign(@point_vector.x) or sign(f.y) isnt sign(@point_vector.y)
             return
           a = @point_vector.dot(@point_vector)
@@ -921,7 +920,7 @@
           if discriminant < 0
             return

-          dist = atom.shape.body.position.distanceSqrd(@point_start)
+          dist = atom.shape.body.p.distanceSqrd(@point_start)
           if not @closest_atom? or dist < closest_dist
             @closest_atom = atom
             closest_dist = dist
@@ -931,7 +930,7 @@
       if @closest_atom?
         # intersection
         # use the coords of the closest atom
-        @point_end = @closest_atom.shape.body.position
+        @point_end = @closest_atom.shape.body.p.clone()
       else
         # no intersection
         # find the coords at the wall
@@ -944,8 +943,8 @@
         if @point_end.x < 0
           @point_end.x = 0
           @point_end.y = slope * @point_end.x + y_intercept
-        if @point_end.y > @ceiling.body.position.y - @size.y / 2
-          @point_end.y = @ceiling.body.position.y - @size.y / 2
+        if @point_end.y > @ceiling.body.p.y - @size.y / 2
+          @point_end.y = @ceiling.body.p.y - @size.y / 2
           @point_end.x = (@point_end.y - y_intercept) / slope
         if @point_end.y < 0
           @point_end.y = 0
@@ -970,7 +969,7 @@
     moveSprites: =>
       # drawable things
       drawDrawable = (drawable) =>
-        drawable.sprite.pos = drawable.shape.body.position.plus(@pos)
+        drawable.sprite.pos = drawable.shape.body.p.plus(@pos)
         drawable.sprite.pos.y = 600 - drawable.sprite.pos.y
         drawable.sprite.rotation = -drawable.shape.body.rot.angle()
         true
@@ -978,21 +977,21 @@
       @bombs.each drawDrawable
       @rocks.each drawDrawable

-      @sprite_man.pos = @man.body.position.plus(@pos)
+      @sprite_man.pos = @man.body.p.plus(@pos)
       @sprite_man.pos.y = 600 - @sprite_man.pos.y
       @sprite_man.rotation = -@man.body.rot.angle()

       @sprite_arm.pos = @arm_pos.plus(@pos)
       @sprite_arm.pos.y = 600 - @sprite_arm.pos.y
-      @sprite_arm.rotation = -@mouse_pos.minus(@man.body.position).angle()
-      if @mouse_pos.x < @man.body.position.x
+      @sprite_arm.rotation = -@mouse_pos.minus(@man.body.p).angle()
+      if @mouse_pos.x < @man.body.p.x
         @sprite_arm.rotation += Math.PI

-      @sprite_tank.pos = @pos.plus(@ceiling.body.position)
+      @sprite_tank.pos = @pos.plus(@ceiling.body.p)
       @sprite_tank.pos.y = 600 - @sprite_tank.pos.y

       if @sprite_claw.visible
-        @sprite_claw.pos = @claw.body.position.plus(@pos)
+        @sprite_claw.pos = @claw.body.p.plus(@pos)
         @sprite_claw.pos.y = 600 - @sprite_claw.pos.y
         @sprite_claw.rotation = -@claw.body.rot.angle()

@@ -1010,14 +1009,14 @@
           if atom.marked_for_deletion
             return
           atom.bonds.each (other, joint) =>
-            @drawLine(context, @pos.plus(atom.shape.body.position), @pos.plus(other.shape.body.position), [0, 0, 1, 1])
+            @drawLine(context, @pos.plus(atom.shape.body.p), @pos.plus(other.shape.body.p), [0, 0, 1, 1])
             true
           true

         if @game.debug
           if @claw_pins
             for claw_pin in @claw_pins
-              @drawLine(context, @pos.plus(claw_pin.a.position).plus(claw_pin.anchr1), @pos.plus(claw_pin.b.position).plus(claw_pin.anchr2), [1, 0, 1, 1])
+              @drawLine(context, @pos.plus(claw_pin.a.p).plus(claw_pin.anchr1), @pos.plus(claw_pin.b.p).plus(claw_pin.anchr2), [1, 0, 1, 1])

         # lazer
         if @lazer_line?
@@ -1115,6 +1114,7 @@

     update: (dt) =>
       mouse_pos = @engine.mousePos()
+      mouse_pos.y = 600 - mouse_pos.y
       for tank in @tanks
         tank.mouse_pos = mouse_pos.minus(tank.pos)
         tank.update(dt)


Remove unnecessary null checks on context children since it is guaranteed

--- src/coffee/cilantro/ui/concept/form.coffee
+++ src/coffee/cilantro/ui/concept/form.coffee
@@ -121,7 +121,7 @@
         isContextNonTrivial: ->
             # If the context has no children then we can consider it trivial
             # as it has no filter conditions.
-            if not @context.children?.models?.length == 0
+            if not @context.children.length == 0
                 return false

             # If there are children, the only case a context will be considered


tests for round controller

tests for round controller

--- src/app/rounds/rounds.spec.coffee
+++ src/app/rounds/rounds.spec.coffee
@@ -2,21 +2,38 @@

 	scope = undefined
 	httpBackend = undefined
+	storage = undefined
+
 	beforeEach module("tumblrGame", ($provide) ->
 		$provide.value "TagsService",
-	   	random_tag: ->
-	      	'lotr'
-	   $provide.value "RandomDateService",
-	   	fromPastMonths: ->
-	      	'1391212800000'
+			random_tag: ->
+		   	'lotr'
+		$provide.value "RandomDateService",
+			fromPastMonths: ->
+		   	'1391212800000'
+
+		store = {
+			"current_round": 1,
+			"score": 0
+		}
+
+		$provide.constant "gameStorage",
+			get: (storageID) ->
+				store[storageID]
+			increment: (storageID, n) ->
+				store[storageID] = store[storageID] + n
+
 		return
 	)
-
 	# create the custom mocks on the root scope
 	beforeEach angular.mock.inject(($rootScope, _$httpBackend_, $state) ->
 		scope = $rootScope.$new()
 		scope.httpBackend = _$httpBackend_
 		scope.$state = $state
+	)
+
+	beforeEach inject((gameStorage) ->
+	  storage = gameStorage
 	)

 	afterEach ->
@@ -29,8 +46,9 @@
 			$controller "RoundCtrl",
 			  $scope: scope
 			  $state: scope.$state
+			  gameStorage: storage

-			scope.httpBackend.expectJSONP("http://api.tumblr.com/v2/tagged?api_key=iI6dl4tEgEt96yvRl1urojakH0Wk86544k2ooTuNxHxVGysBMm&tag=lotr&before=1391212800000&callback=JSON_CALLBACK").respond {
+			scope.httpBackend.expectJSONP("https://api.tumblr.com/v2/tagged?api_key=iI6dl4tEgEt96yvRl1urojakH0Wk86544k2ooTuNxHxVGysBMm&tag=lotr&before=1391212800000&callback=JSON_CALLBACK").respond {
 			  meta:
 			    status: 200
 			    msg: "OK"
@@ -84,17 +102,36 @@
 				expect(scope.guess.length).toEqual 0
 			it "is not yet correct", ->
 				expect(scope.correct).toEqual false
+			it "reports no rounds won", ->
+				expect(scope.round).toEqual 1

 		describe "Guess has been entered", ->
 			it "updates correct variable when a guess is correct", ->
 				scope.guess = "#lotr"
 				scope.$apply()
 				expect(scope.correct).toEqual true
-			it "refreshes on a correct guess", ->
-				spyOn(scope.$state, "go").andCallThrough()
+			it "does not update correct variable when a guess is incorrect", ->
+				scope.guess = "#fart"
+				scope.$apply()
+				expect(scope.correct).toEqual false
+
+			it "redirects to the end state on a correct guess", ->
+				spyOn(scope.$state, "transitionTo").andCallThrough()
 				scope.guess = "#lotr"
-				expect(scope.$state.go).toHaveBeenCalled
+				scope.$apply()
+				expect(scope.$state.transitionTo).toHaveBeenCalledWith "end", tag: 'lotr', before: '1391212800000'
 			it "does not refresh on an incorrect guess", ->
-				spyOn(scope.$state, "go").andCallThrough()
+				spyOn(scope.$state, "transitionTo").andCallThrough()
 				scope.guess = "#fart"
-				expect(scope.$state.go).not.toHaveBeenCalled+				expect(scope.$state.transitionTo).not.toHaveBeenCalled
+
+			it "increments rounds won on a correct guess", ->
+				spyOn(storage, "increment").andCallThrough()
+				scope.guess = "#lotr"
+				scope.$apply()
+				expect(storage.increment).toHaveBeenCalledWith 'current_round', 1
+			it "does not increment rounds won on an incorrect guess", ->
+				spyOn(storage, "increment").andCallThrough()
+				scope.guess = "#fart"
+				scope.$apply()
+				expect(storage.increment).not.toHaveBeenCalled


hide referrer pie chart label (Could not draw pie with labels contained inside canvas issue)

--- app/assets/javascripts/shortener/views/shortened_urls/shortened_urls_show.js.coffee
+++ app/assets/javascripts/shortener/views/shortened_urls/shortened_urls_show.js.coffee
@@ -1,7 +1,7 @@
 class Shortener.Views.ShortenedUrlsShow extends Backbone.View

   template: JST['shortener/shortened_urls/show']
-
+
   className: "container"

   events:
@@ -29,7 +29,7 @@
     @$("#total_clicks").text(t)

   updateReferrers: (data) =>
-    @pieChart("#referrers", data, {series: {pie: {innerRadius: 0.7, radius: 1}}})
+    @pieChart("#referrers", data, {series: {pie: {innerRadius: 0.7, radius: 1, label: {show: false}}}})

   updateBrowsers: (data) =>
     @barChart("#browsers", data)


Clean table/database

Clean table/database

--- admin/static/coffee/tables/database.coffee
+++ admin/static/coffee/tables/database.coffee
@@ -46,9 +46,6 @@
                 id: r.row 'uuid'

             driver.run query, (error, result) =>
-                console.log '---- err, result -----'
-                console.log error
-                console.log JSON.stringify(result, null, 2)
                 if error?
                     # TODO: We may want to render only if we failed to open a connection
                     @error = error


embed changes, link changes, late in the evening

--- core/utils.coffee
+++ core/utils.coffee
@@ -185,7 +185,7 @@
         # url has no protocol
         url = '//'+url

-      "#{visibleUrl}"
+      "#{visibleUrl}#{if checkForPostSlash then "/…" else ""}"

   putShowMore: (text, l = 500)->
     shortenedText = __utils.shortenText text,