made region identifiers configurable

made region identifiers configurable

--- vendor/assets/javascripts/mercury/region.js.coffee
+++ vendor/assets/javascripts/mercury/region.js.coffee
@@ -6,8 +6,7 @@
     Mercury.log("building #{@type}", @element, @options)

     @document = @window.document
-    @name = @element.attr('id')
-    @collectDataAttributes()
+    @name = @element.attr(Mercury.config.regions.identifier)
     @history = new Mercury.HistoryBuffer()
     @build()
     @bindEvents()
@@ -20,11 +19,6 @@

   focus: ->

-
-  collectDataAttributes: ->
-    @data = {}
-    $(Mercury.config.regionDataAttributes).each (index, item) =>
-      @data[item] = @element.attr('data-' + item)

   bindEvents: ->
     Mercury.on 'mode', (event, options) => @togglePreview() if options.mode == 'preview'
@@ -69,11 +63,11 @@
   togglePreview: ->
     if @previewing
       @previewing = false
-      @element.addClass(Mercury.config.regionClass).removeClass("#{Mercury.config.regionClass}-preview")
+      @element.addClass(Mercury.config.regions.className).removeClass("#{Mercury.config.regions.className}-preview")
       @focus() if Mercury.region == @
     else
       @previewing = true
-      @element.addClass("#{Mercury.config.regionClass}-preview").removeClass(Mercury.config.regionClass)
+      @element.addClass("#{Mercury.config.regions.className}-preview").removeClass(Mercury.config.regions.className)
       Mercury.trigger('region:blurred', {region: @})

@@ -98,10 +92,16 @@
     return snippets

+  dataAttributes: ->
+    data = {}
+    data[attr] = @element.attr('data-' + attr) for attr in Mercury.config.regions.dataAttributes
+    return data
+
+
   serialize: ->
     return {
       type: @type
-      data: @data
+      data: @dataAttributes()
       value: @content(null, true)
       snippets: @snippets()
     }


Support custom S3 endpoints

Support custom S3 endpoints

--- services/filestore/app/coffee/S3PersistorManager.coffee
+++ services/filestore/app/coffee/S3PersistorManager.coffee
@@ -16,10 +16,15 @@
 Errors = require("./Errors")
 _ = require("underscore")
 awsS3 = require "aws-sdk/clients/s3"
+URL = require('url')

 thirtySeconds = 30 * 1000

 buildDefaultOptions = (bucketName, method, key)->
+	if settings.filestore.s3.endpoint
+		endpoint = "#{settings.filestore.s3.endpoint}/#{bucketName}"
+	else
+		endpoint = "https://#{bucketName}.s3.amazonaws.com"
 	return {
 			aws:
 				key: settings.filestore.s3.key
@@ -27,32 +32,47 @@
 				bucket: bucketName
 			method: method
 			timeout: thirtySeconds
-			uri:"https://#{bucketName}.s3.amazonaws.com/#{key}"
+			uri:"#{endpoint}/#{key}"
 	}

-defaultS3Client = new awsS3({
-	credentials:
-		accessKeyId: settings.filestore.s3.key,
-		secretAccessKey: settings.filestore.s3.secret
-})
+getS3Options = (credentials) ->
+	options =
+		credentials:
+			accessKeyId: credentials.auth_key
+			secretAccessKey: credentials.auth_secret
+
+	if settings.filestore.s3.endpoint
+		options.endpoint = settings.filestore.s3.endpoint
+		options.sslEnabled = false
+
+	return options
+
+defaultS3Client = new awsS3(getS3Options({
+	auth_key: settings.filestore.s3.key,
+	auth_secret: settings.filestore.s3.secret
+}))

 getS3Client = (credentials) ->
 	if credentials?
-		return new awsS3({
-			credentials:
-				accessKeyId: credentials.auth_key
-				secretAccessKey: credentials.auth_secret
-		})
+		return new awsS3(getS3Options(credentials))
 	else
 		return defaultS3Client

+getKnoxClient = (bucketName) =>
+	options =
+		key: settings.filestore.s3.key
+		secret: settings.filestore.s3.secret
+		bucket: bucketName
+	if settings.filestore.s3.endpoint
+		endpoint = URL.parse(settings.filestore.s3.endpoint)
+		options.endpoint = endpoint.hostname
+		options.port = endpoint.port
+	return knox.createClient(options)
+
 module.exports =

 	sendFile: (bucketName, key, fsPath, callback)->
-		s3Client = knox.createClient
-			key: settings.filestore.s3.key
-			secret: settings.filestore.s3.secret
-			bucket: bucketName
+		s3Client = getKnoxClient(bucketName)
 		putEventEmiter = s3Client.putFile fsPath, key, (err, res)->
 			if err?
 				logger.err err:err,  bucketName:bucketName, key:key, fsPath:fsPath,"something went wrong uploading file to s3"
@@ -171,10 +191,7 @@
 			_callback = () ->

 		logger.log key: key, bucketName: bucketName, "deleting directory"
-		s3Client = knox.createClient
-			key: settings.filestore.s3.key
-			secret: settings.filestore.s3.secret
-			bucket: bucketName
+		s3Client = getKnoxClient(bucketName)
 		s3Client.list prefix:key, (err, data)->
 			if err?
 				logger.err err:err, bucketName:bucketName, key:key, "something went wrong listing prefix in aws"
@@ -200,10 +217,7 @@

 	directorySize:(bucketName, key, callback)->
 		logger.log bucketName:bucketName, key:key, "get project size in s3"
-		s3Client = knox.createClient
-			key: settings.filestore.s3.key
-			secret: settings.filestore.s3.secret
-			bucket: bucketName
+		s3Client = getKnoxClient(bucketName)
 		s3Client.list prefix:key, (err, data)->
 			if err?
 				logger.err err:err, bucketName:bucketName, key:key, "something went wrong listing prefix in aws"


minor: whitespace

minor: whitespace

--- client/Social/Activity/views/privatemessagelistitem.coffee
+++ client/Social/Activity/views/privatemessagelistitem.coffee
@@ -26,7 +26,7 @@
     @setClass 'with-parent'
     firstReply = @commentBox.controller.getListItems().first

-    return unless firstReply
+    return  unless firstReply

     if @getData().account._id is firstReply.getData().account._id
       firstReply.setClass 'consequent'


Remove trailing whitespaces

Remove trailing whitespaces

--- client/app/Applications/Inbox.kdapplication/AppView.coffee
+++ client/app/Applications/Inbox.kdapplication/AppView.coffee
@@ -89,7 +89,7 @@
           messagesSplit.resizePanel "100%",0

     @on "MessageSelectedFromOutside", (item)=>
-
+
         messagesSplit.resizePanel "33%",0 unless messagesSplit.didResizeBefore
         messagesSplit.didResizeBefore = yes
         @newMessageBar.enableMessageActionButtons()


update copy in feedback modal

update copy in feedback modal

--- components/multi_page/config.coffee
+++ components/multi_page/config.coffee
@@ -3,7 +3,7 @@
     title: 'Auction FAQs'
     description: '
       How can we help you? Below are a few general categories to help you find the answers you’re looking for.\n\n
-      Need more immediate assistance? Please [contact us](mailto:support@artsy.net)
+      Need more immediate assistance? Please [contact us](mailto:specialist@artsy.net)
     '
     pages:
       'Bidding': 'how-auctions-work-bidding'
@@ -26,7 +26,6 @@
     title: 'Artist FAQs'
     description: '
       How can we help you? Below are answers to some of the most common questions from artists.\n\n
-      Need more immediate assistance? Please [contact us](mailto:support@artsy.net)
     '
     pages:
       'How Artsy Works': 'artist-faqs-how-artsy-works'


use withNewUser helper function instead of generating client and converting user

--- workers/social/lib/social/models/computeproviders/machine.test.coffee
+++ workers/social/lib/social/models/computeproviders/machine.test.coffee
@@ -7,6 +7,7 @@

 { daisy
   expect
+  withNewUser
   withDummyClient
   generateUserInfo
   generateRandomString
@@ -506,16 +507,9 @@
       queue = [

         ->
-          withDummyClient { group : 'koding' }, (client_) ->
-            client = client_
-
-            # registering a new user
-            JUser.convert client, userFormData, (err, data) ->
-              expect(err).to.not.exist
-              { account, newToken }      = data
-              client.sessionToken        = newToken
-              client.connection.delegate = account
-              queue.next()
+          withNewUser { userFormData }, (data) ->
+            { client } = data
+            queue.next()

         ->
           # fetching machine instance of newly registered user
@@ -550,34 +544,22 @@
         queue = [

           ->
-            withDummyClient { group : 'koding' }, (client) ->
-
-              # registering a new user
-              JUser.convert client, userFormData, (err, data) ->
-                expect(err).to.not.exist
-                { account, newToken }      = data
-                client.sessionToken        = newToken
-                client.connection.delegate = account
-                queue.next()
-
-          ->
-            withDummyClient { group : 'koding' }, (client) ->
-              anotherClient = client
-
-              # registering another user
-              JUser.convert anotherClient, generateDummyUserFormData(), (err, data) ->
-                expect(err).to.not.exist
-                queue.next()
-
-          ->
-            # fetching owner user's machine
-            fetchMachinesByUsername userFormData.username, (machines) ->
-              machine = machines[0]
-
-              # expecting error when another user attempts to revive users
-              machine.reviveUsers anotherClient, {}, (err) ->
-                expect(err?.message).to.be.equal 'Access denied'
-                queue.next()
+            withNewUser { userFormData }, (data) ->
+              { client } = data
+              queue.next()
+
+          ->
+            withNewUser {}, (data) ->
+              { client : anotherClient } = data
+
+              # fetching owner user's machine
+              fetchMachinesByUsername userFormData.username, (machines) ->
+                machine = machines[0]
+
+                # expecting error when another user attempts to revive users
+                machine.reviveUsers anotherClient, {}, (err) ->
+                  expect(err?.message).to.be.equal 'Access denied'
+                  queue.next()

           -> done()

@@ -597,16 +579,9 @@
         queue = [

           ->
-            withDummyClient { group : 'koding' }, (client_) ->
-              client = client_
-
-              # registering a new user
-              JUser.convert client, userFormData, (err, data) ->
-                expect(err).to.not.exist
-                { account, newToken }      = data
-                client.sessionToken        = newToken
-                client.connection.delegate = account
-                queue.next()
+            withNewUser { userFormData }, (data) ->
+              { client } = data
+              queue.next()

           ->
             # fetching machine of owner user
@@ -640,25 +615,14 @@
         queue = [

           ->
-            withDummyClient { group : 'koding' }, (client_) ->
-              client = client_
-
-              # registering a new user
-              JUser.convert client, userFormData, (err, data) ->
-                expect(err).to.not.exist
-                { account, newToken }      = data
-                client.sessionToken        = newToken
-                client.connection.delegate = account
-                queue.next()
-
-          ->
-            withDummyClient { group : 'koding' }, (client_) ->
-              anotherClient = client_
-
-              # registering another user
-              JUser.convert anotherClient, generateDummyUserFormData(), (err, data) ->
-                expect(err).to.not.exist
-                queue.next()
+            withNewUser { userFormData }, (data) ->
+              { client } = data
+              queue.next()
+
+          ->
+            withNewUser {}, (data) ->
+              { client : anotherClient } = data
+              queue.next()

           ->
             # fetching machine of user
@@ -697,16 +661,9 @@
         queue = [

           ->
-            withDummyClient { group : 'koding' }, (client_) ->
-              client = client_
-
-              # registering user
-              JUser.convert client, userFormData, (err, data) ->
-                expect(err).to.not.exist
-                { account, newToken }      = data
-                client.sessionToken        = newToken
-                client.connection.delegate = account
-                queue.next()
+            withNewUser { userFormData }, (data) ->
+              { client } = data
+              queue.next()

           ->
             fetchMachinesByUsername userFormData.username, (machines) ->
@@ -780,16 +737,9 @@
         queue = [

           ->
-            withDummyClient { group : 'koding' }, (client_) ->
-              client = client_
-
-              # registering a new user
-              JUser.convert client, userFormData, (err, data) ->
-                expect(err).to.not.exist
-                { account, newToken }      = data
-                client.sessionToken        = newToken
-                client.connection.delegate = account
-                queue.next()
+            withNewUser { userFormData }, (data) ->
+              { client } = data
+              queue.next()

           ->
             fetchMachinesByUsername userFormData.username, (machines) ->
@@ -817,34 +767,19 @@
       account             = {}
       userCount           = 0
       userFormData        = generateDummyUserFormData()
-      anotherClient       = {}
       anotherUserFormData = generateDummyUserFormData()

       queue = [

         ->
-          withDummyClient { group : 'koding' }, (client_) ->
-            client = client_
-
-            # registering a new user
-            JUser.convert client, userFormData, (err, data) ->
-              expect(err).to.not.exist
-              { account, newToken }      = data
-              client.sessionToken        = newToken
-              client.connection.delegate = account
-              queue.next()
-
-        ->
-          withDummyClient { group : 'koding' }, (client_) ->
-            anotherClient = client_
-
-            # registering another user
-            JUser.convert anotherClient, anotherUserFormData, (err, data) ->
-              expect(err).to.not.exist
-              { account, newToken }             = data
-              anotherClient.sessionToken        = newToken
-              anotherClient.connection.delegate = account
-              queue.next()
+          withNewUser { userFormData }, (data) ->
+            { client } = data
+            queue.next()
+
+        ->
+          withNewUser {}, (data) ->
+            { userFormData : anotherUserFormData } = data
+            queue.next()

         ->
           fetchMachinesByUsername userFormData.username, (machines) ->


fix coffeelint errors in machine.test.coffee

--- workers/social/lib/social/models/computeproviders/machine.test.coffee
+++ workers/social/lib/social/models/computeproviders/machine.test.coffee
@@ -12,7 +12,7 @@
 { daisy }                     = Bongo
 { createUserAndMachine
   generateMachineParams
-  generateMachineParamsByAccount} = require '../../../../testhelper/models/machinehelper'
+  generateMachineParamsByAccount } = require '../../../../testhelper/models/machinehelper'

 { generateUserInfo
   generateDummyClient
@@ -57,7 +57,7 @@
             expect(err).to.not.exist
             machineParams = data
             queue.next()
-
+
         ->
           # sending machineParams.user null and expecting error
           machineParams.user = null
@@ -66,9 +66,9 @@
             queue.next()

         -> done()
-
+
       ]
-
+
       daisy queue

@@ -94,7 +94,7 @@
               expect(err).to.not.exist
               machineParams = data
               queue.next()
-
+
           ->
             # expecting machine to be craeted
             JMachine.create machineParams, (err, machine) ->
@@ -102,11 +102,11 @@
               expect(machine.provider).to.be.equal machineParams.provider
               expect(machine.status.state).to.be.equal 'NotInitialized'
               queue.next()
-
-          -> done()
-
-        ]
-
+
+          -> done()
+
+        ]
+
         daisy queue

@@ -157,7 +157,7 @@
             machine.destroy _client, (err) ->
               expect(err).to.not.exist
               queue.next()
-
+
           ->
             # fetching machine instances again expecting machine to be destroyed
             JMachine.fetchByUsername userInfo.username, (err, machines) ->
@@ -166,9 +166,9 @@
               queue.next()

           -> done()
-
-        ]
-
+
+        ]
+
         daisy queue

@@ -205,9 +205,9 @@
               queue.next()

           -> done()
-
-        ]
-
+
+        ]
+
         daisy queue

@@ -243,7 +243,7 @@
               targets   : [anotherUser]
               asOwner   : yes
               permanent : yes
-
+
             machine.addUsers params, (err) ->
               expect(err).to.not.exist
               expect(machine.users.length).to.be.equal userCount + 1
@@ -284,7 +284,7 @@
             targets   : targets
             asOwner   : yes
             permanent : yes
-
+
           expectedError = "Machine sharing is limited up to #{limit} users."
           machine.addUsers params, (err) ->
             expect(err?.message).to.be.equal expectedError
@@ -329,7 +329,7 @@
             targets   : targets
             asOwner   : yes
             permanent : yes
-
+
           machine.addUsers params, (err) ->
             expect(err).to.not.exist
             expect(machine.users.length).to.be.equal userCount + addedUserCount
@@ -341,7 +341,7 @@
             targets   : targets.slice 0, removedUserCount
             asOwner   : yes
             permanent : yes
-
+
           expectedUserCount = userCount + addedUserCount - removedUserCount
           machine.removeUsers params, (err) ->
             expect(err).to.not.exist
@@ -369,7 +369,7 @@
               expect(err).to.not.exist
               machine = machine_
               queue.next()
-
+
           ->
             # expecting error when target is not specified
             params = { target : null }
@@ -396,7 +396,7 @@
               expect(err).to.not.exist
               machine = machine_
               queue.next()
-
+
           ->
             # expecting error when target is invalid
             params = { target : ['invalidTarget'] }
@@ -433,7 +433,7 @@
               expect(err).to.not.exist
               anotherUser = user_
               queue.next()
-
+
           ->
             # expecting user to be added when asUser is yes
             params = { target : [anotherUser.username], asUser : yes }
@@ -988,7 +988,7 @@
             anotherUser        = user_
             anotherUserAccount = account_
             queue.next()
-
+
         ->
           # sharing maching with another user
           params = { target : [anotherUser.username], asUser : yes }
@@ -1047,7 +1047,7 @@
             anotherUser        = user_
             anotherUserAccount = account_
             queue.next()
-
+
         ->
           # sharing machine with another user
           params = { target : [anotherUser.username], asUser : yes }


homepage restored

homepage restored

--- client/landing/app/MainApp/KodingMainViewController.coffee
+++ client/landing/app/MainApp/KodingMainViewController.coffee
@@ -103,8 +103,8 @@
     mainView = @mainViewController.getView()
     @loginScreen.slideUp =>
       @mainViewController.sidebarController.accountChanged account
-      # appManager.openApplication "Activity", yes
-      appManager.openApplication "Demos", yes
+      appManager.openApplication "Activity", yes
+      # appManager.openApplication "Demos", yes
       @mainViewController.getView().decorateLoginState yes

   goToPage:(publishingInstance,event)=>


add limits to acceptable policy

add limits to acceptable policy

--- client/landing/site.landing/coffee/legal/userpolicy.coffee
+++ client/landing/site.landing/coffee/legal/userpolicy.coffee
@@ -22,13 +22,6 @@

-        Quotas and Limits

-        The Service is intended for development purposes and is not a production
-        platform. We operate on shared servers and so we will rapidly suspend
-        accounts that generate sustained high levels of load.
-

-
-

         Prohibited Content

         The Content displayed and/or processed through your Application or other
         web site utilizing the Service shall not contain any of the following
@@ -253,6 +246,18 @@

+

+        Quotas and Limits

+        The Service is intended for development purposes and is not a production
+        platform. We operate on shared servers and so we will rapidly suspend
+        accounts that generate sustained high levels of load or use provided
+        resources in a way that deteriorates the experience for other users.
+
+
Disk: 3GB per VM (or what your paid account comes with)

+
Ram: 1GB per VM

+
Network output: 7GB/week (free accounts), 14GB/week (paid accounts)

+

+
       Koding reserves the right to suspend or terminate accounts found in violation of these items with or without prior notice given and without liability.

       In addition, Koding reserves the right to terminate unused VMs after a period of inactivity (Usually 45-60 days). Inactive accounts will


compact null

compact null

--- vendor/assets/javascripts/fustrate/_prototypes.coffee
+++ vendor/assets/javascripts/fustrate/_prototypes.coffee
@@ -2,7 +2,7 @@

 Array::compact = (strings = true) ->
   @forEach (element, index) =>
-    @splice(index, 1) if element is undefined or (strings and element is '')
+    @splice(index, 1) if element in [undefined, null] or (strings and element is '')

   @


Deprecating a couple of devices

Deprecating a couple of devices

--- framer/Components/DeviceComponent.coffee
+++ framer/Components/DeviceComponent.coffee
@@ -829,6 +829,7 @@
 	devicePixelRatio: 2
 	deviceType: "phone"
 	minStudioVersion: 71
+	maxStudioVersion: iPhoneXReleaseVersion - 1
 	hands:
 		"iphone-hands-2":
 			width: 2400
@@ -846,6 +847,7 @@
 	devicePixelRatio: 3
 	deviceType: "phone"
 	minStudioVersion: 71
+	maxStudioVersion: iPhoneXReleaseVersion - 1
 	hands:
 		"iphone-hands-2":
 			width: 3987
@@ -937,6 +939,7 @@
 	devicePixelRatio: 2
 	deviceType: "phone"
 	minStudioVersion: newDeviceMinVersion
+	maxStudioVersion: iPhoneXReleaseVersion - 1
 	hands:
 		"iphone-hands-2":
 			width: 2362
@@ -994,6 +997,7 @@
 	devicePixelRatio: 2.627
 	deviceType: "phone"
 	minStudioVersion: googlePixelReleaseVersion
+	maxStudioVersion: iPhoneXReleaseVersion - 1
 	hands:
 		"iphone-hands-2":
 			width: 3344


Make sure we close all connections

Make sure we close all connections

--- src/feature.spec.coffee
+++ src/feature.spec.coffee
@@ -28,6 +28,7 @@

   after ->
     socketIORemoteEndpoint.close()
+    socketIORemoteClient.disconnect()
     require._cache = {}


Cleanup seeds specs.

Cleanup seeds specs.

--- spec/javascripts/application/seeds_store_spec.js.coffee
+++ spec/javascripts/application/seeds_store_spec.js.coffee
@@ -12,37 +12,42 @@
     subject.update([{name: 1}, {name: 2}])
     expect(subject.readPod(1)).eventually.eql([{name: 1}]).notify(done)
   describe 'readsFromAll', ->
-    listByName = [
-      {name: 1, category: "a", stars: 1},
-      {name: 2, category: "c", stars: 5},
-      {name: 3, category: "b", stars: 4},
-      {name: 4, category: "c", stars: 6},
-      {name: 5, category: "c", stars: 3},
-      {name: 6, category: "c", stars: 2}
-    ]
-    listByCategory = [
-      listByName[0],
-      listByName[2],
-      listByName[1],
-      listByName[3],
-      listByName[4],
-      listByName[5]
-    ]
-    listByStars = [
-      listByName[0],
-      listByName[5],
-      listByName[4],
-      listByName[2],
-      listByName[1],
-      listByName[3]
-    ]
+    listByName = ->
+      [
+        {name: 1, category: "a", stars: 1},
+        {name: 2, category: "c", stars: 5},
+        {name: 3, category: "b", stars: 4},
+        {name: 4, category: "c", stars: 6},
+        {name: 5, category: "c", stars: 3},
+        {name: 6, category: "c", stars: 2}
+      ]
+    listByCategory = ->
+      list = listByName()
+      [
+        list[0],
+        list[2],
+        list[1],
+        list[3],
+        list[4],
+        list[5]
+      ]
+    listByStars = ->
+      list = listByName()
+      [
+        list[0],
+        list[5],
+        list[4],
+        list[2],
+        list[1],
+        list[3]
+      ]
     beforeEach (done) ->
-      expect(subject.update(listByName))
+      expect(subject.update(listByName()))
         .eventually.notify(done)
     describe 'name', ->
-      list = listByName
       sortBy = 'name'
       describe 'asc', ->
+        list = listByName()
         sortOrderAsc = true
         it 'from the beginning', (done) ->
           expect(subject.readFromAll(sortBy, sortOrderAsc, 0, 1)).eventually
@@ -54,20 +59,21 @@
           expect(subject.readFromAll(sortBy, sortOrderAsc, 5, 2)).eventually
             .eql([list[5]]).notify(done)
       describe 'desc', ->
+        list = listByName().reverse()
         sortOrderAsc = false
         it 'from the beginning', (done) ->
           expect(subject.readFromAll(sortBy, sortOrderAsc, 0, 1)).eventually
-            .eql([list[5]]).notify(done)
+            .eql([list[0]]).notify(done)
         it 'in the middle', (done) ->
           expect(subject.readFromAll(sortBy, sortOrderAsc, 1, 2)).eventually
-            .eql([list[4], list[3]]).notify(done)
+            .eql([list[1], list[2]]).notify(done)
         it 'beyond scope', (done) ->
           expect(subject.readFromAll(sortBy, sortOrderAsc, 5, 2)).eventually
-            .eql([list[0]]).notify(done)
+            .eql([list[5]]).notify(done)
     describe 'stars', ->
-      list = listByStars
       sortBy = 'stars'
       describe 'asc', ->
+        list = listByStars()
         sortOrderAsc = true
         it 'from the beginning', (done) ->
           expect(subject.readFromAll(sortBy, sortOrderAsc, 0, 1)).eventually
@@ -79,13 +85,15 @@
           expect(subject.readFromAll(sortBy, sortOrderAsc, 5, 2)).eventually
             .eql([list[5]]).notify(done)
       describe 'desc', ->
+        list = listByStars().reverse()
         sortOrderAsc = false
         it 'from the beginning', (done) ->
           expect(subject.readFromAll(sortBy, sortOrderAsc, 0, 1)).eventually
-            .eql([list[5]]).notify(done)
+            .eql([list[0]]).notify(done)
         it 'in the middle', (done) ->
           expect(subject.readFromAll(sortBy, sortOrderAsc, 1, 2)).eventually
-            .eql([list[4], list[3]]).notify(done)
+            .eql([list[1], list[2]]).notify(done)
         it 'beyond scope', (done) ->
           expect(subject.readFromAll(sortBy, sortOrderAsc, 5, 2)).eventually
-            .eql([list[0]]).notify(done)
+            .eql([list[5]]).notify(done)
+


* minor changes.

* minor changes.

--- test/fake-nosql.coffee
+++ test/fake-nosql.coffee
@@ -1,6 +1,6 @@
 EncodingNoSQL   = require 'nosql-encoding'
 sinon           = require 'sinon'
-inherits        = require("abstract-object/lib/util/inherits")
+inherits        = require 'abstract-object/lib/util/inherits'
 FakeIterator    = require './fake-iterator'

 module.exports = class FakeDB


fixed uppercade 'V' in onInvalid

fixed uppercade 'V' in onInvalid

--- src/jquery-ean13.coffee
+++ src/jquery-ean13.coffee
@@ -42,7 +42,7 @@
       if @validate()
         @settings.onValid.call()
       else
-        @settings.onInValid.call()
+        @settings.onInvalid.call()

       # call getCode method
       code = @getCode()


Added very large file testing

Added very large file testing

--- test/files/stream.iced
+++ test/files/stream.iced
@@ -87,3 +87,34 @@

 		T.equal(stock, encoded_data, "inconsistency found: data=#{data}")
 	cb()
+
+#==========================================================
+#These tests check the consistency and output of a very very large file
+#==========================================================
+
+exports.test_giant_file_consistency = (T, cb) ->
+	encoder = new stream.StreamEncoder(new enc.Encoding(b64enc.alphabet, b64enc.in_block_len))
+	decoder = new stream.StreamDecoder(new enc.Encoding(b64enc.alphabet, b64enc.in_block_len))
+	encoder.pipe(decoder)
+	data_string = ''
+	data_string += Math.floor(Math.random()*10) for j in [0...100000]
+	data = new Buffer(data_string)
+
+	encoder.write(data)
+	decoded_data = decoder.read()
+
+	T.equal(data, decoded_data, "inconsistency found: data=#{data}")
+	cb()
+
+exports.test_giant_file_output = (T, cb) ->
+	encoder = new stream.StreamEncoder(new enc.Encoding(b64enc.alphabet, b64enc.in_block_len))
+	data_string = ''
+	data_string += Math.floor(Math.random()*10) for j in [0...100000]
+	data = new Buffer(data_string)
+	stock = b64stripped.encode(data)
+
+	encoder.write(data)
+	encoded_data = encoder.read().toString()
+
+	T.equal(stock, encoded_data, "inconsistency found: data=#{data}")
+	cb()


invitation: create: refactored to pass tests and coffeelint

--- workers/social/lib/social/models/invitation.coffee
+++ workers/social/lib/social/models/invitation.coffee
@@ -153,80 +153,96 @@
   # triggers sendInvitationEmail
   @create: permit 'send invitations',
     success: (client, options, callback) ->
-      JUser        = require './user'
-      JGroup       = require './group'
-
-      { delegate } = client.connection
-      groupName    = client.context.group or 'koding'
-      name         = getName delegate
-      codes        = []
-
-      { invitations, forceInvite, returnCodes, noEmail } = options
-
-
-      # check for membership
+      JGroup    = require './group'
+      groupName = client.context.group or 'koding'
+
       JGroup.one { slug: groupName }, (err, group) ->
         return callback new KodingError err                   if err
         return callback new KodingError 'group doesnt exist'  if not group

-
-        queue = invitations.map (invitation) -> (fin) ->
-
-          { email, firstName, lastName, role } = invitation
-
-          JInvitation.one { email, groupName }, (err, invitation) ->
-            return fin new KodingError err  if err
-
-            # do not send another invitation if the user is already invited
-            # before
-
-            if invitation
-              if forceInvite
-                invitation.remove()
-              else
-                codes.push { email, code: invitation.code, alreadyInvited: yes }
-                return fin()
-
-            isAlreadyMember group, email, (err, isMember) ->
-              return fin new KodingError err  if err
-
-              # do not send another invitation if the user is already a member
-              # of the group
-              return fin()  if isMember
-
-              hash = JUser.getHash email
-
-              # eg: VJPj9gUQ
-              code = shortid.generate()
-
-              data = {
-                code
-                hash
-                email
-                groupName
-                role
-              }
-              # firstName and lastName are optional
-              data.firstName = firstName  if firstName
-              data.lastName  = lastName   if lastName
-
-              invite = new JInvitation data
-              invite.save (err) ->
-                return fin new KodingError err  if err
-                codes.push { email, code }
-
-                return fin()  if noEmail
-
-                JInvitation.sendInvitationEmail client, invite, -> fin()
-
-
-        async.parallel queue, (err) ->
+        { invitations, forceInvite, returnCodes, noEmail } = options
+
+        queue = invitations.map (invitationData) -> (end) ->
+          invitationData.forceInvite = forceInvite
+          invitationData.noEmail     = noEmail
+          invitationData.groupName   = groupName
+
+          createSingleInvite client, group, invitationData, end
+
+        async.parallel queue, (err, codes) ->
           return callback err  if err
-          if returnCodes then callback err, codes else callback()
-
-
-  # isAlreadyMember checks if the given email is already member of the given
-  # group
+          return callback()  unless returnCodes
+          return callback null, codes
+
+
+
+  createSingleInvite = (client, group, invitationData, end) ->
+    { email, role, forceInvite, noEmail } = invitationData
+
+    groupName  = group.slug
+    inviteInfo = null
+
+    queue = [
+      (fin) -> JInvitation.one { email, groupName }, fin
+
+    , (invite, fin) ->
+      [fin, invite] = [invite, fin]  unless fin
+
+      return fin null, no  unless invite
+      return invite.remove fin  if forceInvite
+
+      inviteInfo = { email, code: invite.code, alreadyInvited: yes }
+      return fin null, yes
+
+    , (alreadyInvited, fin) ->
+      [fin, alreadyInvited] = [alreadyInvited, fin]  unless fin
+
+      return fin null, yes  if alreadyInvited
+      isAlreadyMember group, email, fin
+
+    , (alreadyMember, fin) ->
+      [fin, alreadyMember] = [alreadyMember, fin]  unless fin
+
+      return fin null, no  if alreadyMember
+      createInviteInstance invitationData, fin
+
+    , (invite, fin) ->
+      [fin, invite] = [invite, fin]  unless fin
+      inviteInfo = { email, code: invite.code }  if invite
+
+      return fin()  if noEmail or not invite
+      JInvitation.sendInvitationEmail client, invite, fin
+
+    ]
+
+    async.waterfall queue, (err) ->
+      return end new KodingError err  if err
+      return end null, inviteInfo
+
+
+  createInviteInstance = (options, callback) ->
+
+    { email, groupName, role, firstName, lastName } = options
+
+    JUser = require './user'
+    hash  = JUser.getHash email
+
+    # eg: VJPj9gUQ
+    code = shortid.generate()
+    data = { code, hash, email, groupName, role }
+
+    # firstName and lastName are optional
+    data.firstName = firstName  if firstName
+    data.lastName  = lastName   if lastName
+
+    invite = new JInvitation data
+    invite.save (err) ->
+      return callback new KodingError err  if err
+      return callback null, invite
+
+
+  # isAlreadyMember checks if the given email
+  # is already member of the given group
   isAlreadyMember = (group, email, callback) ->
     return callback new KodingError 'group is not set'  if not group
     return callback new KodingError 'email is not set'  if not email


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/tween.coffee
+++ src/tween.coffee
@@ -16,7 +16,7 @@
     for property, data of I.activeTweens
       if I.age >= data.endTime
         I[property] = data.end
-        I.activeTweens[property].
+        I.activeTweens[property].complete?()
         delete I.activeTweens[property]
       else
         f = Easing[data.easing](data.start, data.end)


fix task dates, task duplications in front end on update

--- src/app/units/partials/directives/unit-modals.coffee
+++ src/app/units/partials/directives/unit-modals.coffee
@@ -25,7 +25,6 @@
             alertService.add("danger", "Error: " + response.data.error, 5000)
       )
     else
-
       Tutorial.update( { id: tutorial.id, tutorial: save_data } ).$promise.then (
         (response) ->
           $modalInstance.close(response)
@@ -89,7 +88,22 @@

   $scope.removeUpReq = (upReq) ->
     $scope.task.upload_requirements = $scope.task.upload_requirements.filter (anUpReq) -> anUpReq.key isnt upReq.key
-
+
+  populate_task = (oldTask, newTask) ->
+    _.extend(oldTask, newTask)
+    if newTask.abbreviation
+      oldTask.abbr = newTask.abbreviation
+    else
+      oldTask.abbr = newTask.abbr
+    if newTask.weighting
+      oldTask.weight = newTask.weighting
+    else
+      oldTask.weight = newTask.weight
+    oldTask.name = newTask.name
+    oldTask.upload_requirements = newTask.upload_requirements
+    oldTask.target_date = newTask.target_date
+    oldTask.required = newTask.required
+
   $scope.saveTask = () ->
     # Map the task to upload to the appropriate fields
     task = $scope.task
@@ -97,6 +111,11 @@
     task.weighting = $scope.task.weight
     task.unit_id = $scope.unit.id
     task.upload_requirements = JSON.stringify $scope.task.upload_requirements
+    # task.upload_requirements = $scope.task.upload_requirements
+    if task.target_date && task.target_date.getMonth
+      tgt = task.target_date
+      task.target_date = "#{tgt.getFullYear()}-#{tgt.getMonth() + 1}-#{tgt.getDate()}"
+
     task.description = $scope.task.desc

     if $scope.isNew
@@ -115,8 +134,8 @@
       TaskDefinition.update( { id: task.id, task_def: task } ).$promise.then (
         (response) ->
           $modalInstance.close(response)
-          $scope.unit.task_definitions.push(response)
           alertService.add("success", "#{response.name} Updated", 5000)
+          populate_task($scope.task, response)
       ),
       (
         (response) ->


Attach to a moar better container

Attach to a moar better container

--- source/coffee/index.coffee
+++ source/coffee/index.coffee
@@ -224,7 +224,7 @@
       opts =
         title: data().description
         trigger: "hover"
-        container: "#inputForm"
+        container: "#indexInputModal"
         animation: false
       $(el).tooltip opts


Issue #6447: Docker non stoppé après le timeout

The child_process.exec() API was not followed, so the process was not stopped.

Switch the exec call into a child_process.spawn() call to avoid issues with shell expansion, and to fix the call itself so that it works. Also call the callback when it's done.

 Correct the API as the timeout is expressed in second, but setTimeout() expects milliseconds. Add a default of 30 seconds.

--- app/coffee/DockerRunner.coffee
+++ app/coffee/DockerRunner.coffee
@@ -1,5 +1,4 @@
 spawn = require("child_process").spawn
-exec  = require("child_process").exec
 logger = require "logger-sharelatex"
 Settings = require "settings-sharelatex"

@@ -22,9 +21,10 @@
     proc = spawn DockerRunner._docker, docker_cmd, stdio: "inherit", cwd: directory
     timer = setTimeout () ->
       logger.warn "timeout achieved, stopping docker instance"
-      exec 'docker', ['stop', "texlive-#{project_id}"]
-      callback timedout: true
-    , timeout
+      proc = spawn 'docker', ['stop', "texlive-#{project_id}"]
+      proc.on "close", ->
+        callback timedout: true
+    , (timeout ?= 30) * 1000
     proc.on "close", () ->
       clearTimeout timer
       callback()


comment out synctex for moment

comment out synctex for moment

--- app/coffee/DockerRunner.coffee
+++ app/coffee/DockerRunner.coffee
@@ -108,8 +108,8 @@
 	_getContainerOptions: (command, image, volumes, timeout, environment) ->
 		timeoutInSeconds = timeout / 1000

-		if Settings.path?.synctexBinHostPath?
-			volumes[Settings.path.synctexBinHostPath] = "/opt/synctex:ro"
+		# if Settings.path?.synctexBinHostPath?
+		# 	volumes[Settings.path.synctexBinHostPath] = "/opt/synctex:ro"

 		dockerVolumes = {}
 		for hostVol, dockerVol of volumes


:lipstick: Use switch

:lipstick: Use switch

--- build/tasks/codesign-task.coffee
+++ build/tasks/codesign-task.coffee
@@ -22,13 +22,14 @@
     spawn {cmd, args}, (error) -> callback(error)

   signApp = (callback) ->
-    if process.platform is 'darwin'
-      cmd = 'codesign'
-      args = ['-f', '-v', '-s', 'Developer ID Application: GitHub', grunt.config.get('atom.shellAppDir')]
-      spawn {cmd, args}, (error) -> callback(error)
-    else if process.platform is 'win32'
-      cmd = 'signtool'
-      args = [path.join(grunt.config.get('atom.shellAppDir'), 'atom.exe')]
-      spawn {cmd, args}, (error) -> callback(error)
-    else
-      callback()
+    switch process.platform
+      when 'darwin'
+        cmd = 'codesign'
+        args = ['-f', '-v', '-s', 'Developer ID Application: GitHub', grunt.config.get('atom.shellAppDir')]
+        spawn {cmd, args}, (error) -> callback(error)
+      when 'win32'
+        cmd = 'signtool'
+        args = [path.join(grunt.config.get('atom.shellAppDir'), 'atom.exe')]
+        spawn {cmd, args}, (error) -> callback(error)
+      else
+        callback()


Only codesign on Mac OS X

Only codesign on Mac OS X

--- build/tasks/codesign-task.coffee
+++ build/tasks/codesign-task.coffee
@@ -2,6 +2,8 @@
   {spawn} = require('./task-helpers')(grunt)

   grunt.registerTask 'codesign', 'Codesign the app', ->
+    return unless process.platform is 'darwin'
+
     done = @async()

     if process.env.XCODE_KEYCHAIN


Only call `getOriginURL` once

Only call `getOriginURL` once

--- index.coffee
+++ index.coffee
@@ -13,8 +13,9 @@
 getOriginURL = -> atom.project.getRepositories()[0]?.getOriginURL() or null

 isGitHubRepo = ->
-  return false unless getOriginURL()
-  m = getOriginURL().match GH_REGEX
+  u = getOriginURL()
+  return false unless u
+  m = u.match GH_REGEX
   if m
     {
       user: m[3]


allow choice to have a wrapping action

allow choice to have a wrapping action

--- index.coffee
+++ index.coffee
@@ -35,18 +35,19 @@

   for rule, ruleIndex in rules
     newValue = override[rule.name]
+    newValueIsArray = Array.isArray newValue
     continue  unless newValue?

-    if rule.expression.type is 'choice'
+    if newValueIsArray and rule.expression.type is 'choice'
       alternatives = rule.expression.alternatives
-      unless Array.isArray newValue
-        throw new Error "Rule #{rule.name} mismatch (needs alternatives)"
       if alternatives.length isnt newValue.length
         throw new Error "Rule #{rule.name} mismatch (alternatives #{alternatives.length} != #{newValue.length}"
       for alternative, alternativeIndex in alternatives
         alternatives[alternativeIndex] = overrideAction alternative, newValue[alternativeIndex]
+    else if not newValueIsArray
+      rule.expression = overrideAction rule.expression, newValue
     else
-      rule.expression = overrideAction rule.expression, newValue
+      throw new Error "Rule #{rule.name} mismatch (needs no alternatives)"
   ast


Reset game after showing it

Reset game after showing it

--- index.coffee
+++ index.coffee
@@ -184,6 +184,7 @@
 finish_strategy = (context)->
   track_strategy(context, 'finish')
   if !context.game_status.ended or !context.game_status.starved
+    context.game_responses = {}
     show_game_status(context)
     return no


Revise git statuses.

Revise git statuses.

--- index.coffee
+++ index.coffee
@@ -2,31 +2,6 @@
 _ = require 'underscore'

 repo = git.open '.'
-
-STATUS_CODE_MAP =
-  1:
-    desc: 'added'
-    staged: true
-  2:
-    desc: 'modified'
-    staged: true
-  4:
-    desc: 'deleted'
-    staged: true
-  128:
-    desc: 'added'
-    staged: false
-  256:
-    desc: 'modified'
-    staged: false
-  512:
-    desc: 'deleted'
-    staged: false
-
-_filter_statuses_by_desc = (statuses, desc) ->
-  _.filter statuses, (status) ->
-    STATUS_CODE_MAP[status.code]?.desc is desc
-

 module.exports = (Impromptu, register, git) ->
   # Expose the repo object from the git-utils library
@@ -81,7 +56,59 @@
       git._aheadBehind (err, aheadBehind) ->
         done err, aheadBehind?.behind

-  # Returns an array of objects with 'path', 'code', 'staged', 'desc'
+  class Statuses
+    constructor: (@statuses) ->
+      @modified = @_filterByState 'modified'
+      @added = @_filterByState 'added'
+      @deleted = @_filterByState 'deleted'
+
+    toString: ->
+      results = []
+      results.push @modified if @modified.length
+      results.push @added if @added.length
+      results.push @deleted if @deleted.length
+      results.join ' '
+
+    _filterByState: (@state) ->
+      filtered = _.filter @statuses, (status) ->
+        Statuses.CODE_MAP[status.code]?.state is state
+
+      if Statuses.formatters[state]
+        filtered.toString = Statuses.formatters[state]
+
+      filtered
+
+    @CODE_MAP:
+      1:
+        state: 'added'
+        staged: true
+      2:
+        state: 'modified'
+        staged: true
+      4:
+        state: 'deleted'
+        staged: true
+      128:
+        state: 'added'
+        staged: false
+      256:
+        state: 'modified'
+        staged: false
+      512:
+        state: 'deleted'
+        staged: false
+
+    @formatters:
+      added: ->
+        if @length then "+#{@length}" else ""
+
+      modified: ->
+        if @length then "∆#{@length}" else ""
+
+      deleted: ->
+        if @length then "-#{@length}" else ""
+
+  # Returns an array of objects with 'path', 'code', 'staged', 'state'
   #
   # This command *must* be passed through a formatter before its displayed
   register '_status',
@@ -97,17 +124,27 @@

         path: path
         code: code
-        staged: STATUS_CODE_MAP[code]?.staged
-        desc: STATUS_CODE_MAP[code]?.desc
+        staged: Statuses.CODE_MAP[code]?.staged
+        state: Statuses.CODE_MAP[code]?.state

       done null, statuses
+
+  register 'staged',
+    update: (done) ->
+      git._status (err, statuses) ->
+        done err, new Statuses _.where(statuses, {staged: true})
+
+  register 'unstaged',
+    update: (done) ->
+      git._status (err, statuses) ->
+        done err, new Statuses _.where(statuses, {staged: false})

   # Get the number of "untracked" files
   # Untracked is defined as new files that are not staged
   register 'untracked',
     update: (done) ->
       git._status (err, statuses) ->
-        statuses = _filter_statuses_by_desc(statuses, 'added')
+        statuses = _filter_statuses_by_state(statuses, 'added')
         count = _.where(statuses, {staged: false}).length
         done err, count

@@ -116,7 +153,7 @@
   register 'modified',
     update: (done) ->
       git._status (err, statuses) ->
-        count = _filter_statuses_by_desc(statuses, 'modified').length
+        count = _filter_statuses_by_state(statuses, 'modified').length
         done err, count

   # Get the number of deleted files
@@ -124,7 +161,7 @@
   register 'deleted',
     update: (done) ->
       git._status (err, statuses) ->
-        count = _filter_statuses_by_desc(statuses, 'deleted').length
+        count = _filter_statuses_by_state(statuses, 'deleted').length
         done err, count

   # Get the number of "added" files
@@ -132,6 +169,6 @@
   register 'added',
     update: (done) ->
       git._status (err, statuses) ->
-        statuses = _filter_statuses_by_desc statuses, 'added'
+        statuses = _filter_statuses_by_state statuses, 'added'
         count = _.where(statuses, {staged: true}).length
         done err, count


Re-enable voxel-sfx, works with game.materials.artPacks compatibility

--- index.coffee
+++ index.coffee
@@ -161,7 +161,7 @@
     'voxel-health-bar': {}
     #'voxel-health-fall': {} # requires voxel-player
     'voxel-food': {}
-    #'voxel-sfx': {} # requires voxel-texture-shader, game.materials artpacks
+    'voxel-sfx': {}
     'voxel-fly': {flySpeed: 0.8, onDemand: true}
     'voxel-gamemode': {}
     #'voxel-walk': {} # requires controls target


close the button group when opening the content assets picker

--- app/assets/javascripts/locomotive/views/inputs/file_view.js.coffee
+++ app/assets/javascripts/locomotive/views/inputs/file_view.js.coffee
@@ -34,6 +34,8 @@

   open_content_assets_drawer: (event) ->
     event.stopPropagation() & event.preventDefault()
+
+    $(event.target).closest('.btn-group').removeClass('open')

     window.application_view.drawer_view.open(
       $(event.target).attr('href'),


Increase font-size of spec-runner feedback

--- user/config.cson
+++ user/config.cson
@@ -85,3 +85,6 @@
     showOnStartup: false
   whitespace:
     removeTrailingWhitespace: false
+".atom-mocha":
+  editor:
+    fontSize: 13


ConfirmationChoice: Support "tooltip" on the choices.

--- src/elements/ConfirmationChoice/ConfirmationChoice.coffee
+++ src/elements/ConfirmationChoice/ConfirmationChoice.coffee
@@ -47,6 +47,8 @@
 			check: Icon
 		choice:
 			check: String
+		tooltip:
+			check: "PlainObject"
 		cancel:
 			default: false
 			check: Boolean
@@ -73,6 +75,7 @@
 				left: true
 				value: choice
 				disabled: choice.disabled
+				tooltip: choice.tooltip
 				onClick: (ev, btn) =>
 					@__choice = btn.getValue()
 					CUI.chainedCall(


Add device overall_status & overall_progress tests

Change-type: patch
Signed-off-by: Thodoris Greasidis <5791bdc4541e79bd43d586e3c3eff1e39c16665f@balena.io>

--- tests/integration/models/device.spec.coffee
+++ tests/integration/models/device.spec.coffee
@@ -230,6 +230,13 @@
 						m.chai.expect(device.id).to.equal(@device.id)
 						m.chai.expect(device.device_name).to.equal(undefined)

+				it 'should be able to retrieve computed terms', ->
+					balena.models.device.getAll($select: [ 'overall_status', 'overall_progress' ])
+					.then ([ device ]) ->
+						m.chai.expect(device).to.deep.match
+							overall_status: 'inactive'
+							overall_progress: null
+
 			describe 'balena.models.device.getAllByApplication()', ->

 				[
@@ -257,6 +264,13 @@
 						m.chai.expect(device.id).to.equal(@device.id)
 						m.chai.expect(device.device_name).to.equal(undefined)

+				it 'should be able to retrieve computed terms', ->
+					balena.models.device.getAllByApplication(@application.id, $select: [ 'overall_status', 'overall_progress' ])
+					.then ([ device ]) ->
+						m.chai.expect(device).to.deep.match
+							overall_status: 'inactive'
+							overall_progress: null
+
 			describe 'balena.models.device.get()', ->

 				it 'should be able to get the device by uuid', ->
@@ -284,6 +298,13 @@
 					.then (device) =>
 						m.chai.expect(device.id).to.equal(@device.id)
 						m.chai.expect(device.device_name).to.equal(undefined)
+
+				it 'should be able to retrieve computed terms', ->
+					balena.models.device.get(@device.uuid, $select: [ 'overall_status', 'overall_progress' ])
+					.then (device) ->
+						m.chai.expect(device).to.deep.match
+							overall_status: 'inactive'
+							overall_progress: null

 			describe 'balena.models.device.getByName()', ->

@@ -1272,6 +1293,28 @@
 			it 'should be rejected if the device exists but is inaccessible', ->
 				promise = balena.models.device.getSupervisorState(@device.id)
 				m.chai.expect(promise).to.be.rejectedWith('No online device(s) found')
+
+	describe 'given a single application with a single online device', ->
+
+		givenAnApplicationWithADevice(before)
+
+		before ->
+			balena.pine.patch
+				resource: 'device'
+				id: @device.id
+				body:
+					is_online: true
+					os_variant: 'prod'
+					os_version: 'Resin OS 2.7.8+rev1'
+
+		describe 'balena.models.device.get()', ->
+
+			it 'should be able to retrieve computed terms', ->
+				balena.models.device.get(@device.uuid, $select: [ 'overall_status', 'overall_progress' ])
+				.then (device) ->
+					m.chai.expect(device).to.deep.match
+						overall_status: 'idle'
+						overall_progress: null

 	describe 'given a multicontainer application', ->


Copy raw folder(s)

Copy raw folder(s)

--- plugins/xcopy/xcopy.coffee
+++ plugins/xcopy/xcopy.coffee
@@ -5,4 +5,16 @@
   BasePlugin.extend
     name: "xcopy"

+    config:
+      raw:
+        # src: 'raw'  # Default = key
+        skip: false   # Can be disabled...
+
     writeAfter: ->
+      docpad = @docpad
+      cfg = docpad.getConfig()
+      for k, v of @getConfig() when not v.skip
+        src = path.resolve cfg.srcPath, v.src or k
+        dst = path.resolve cfg.outPath, v.dst or ''
+        docpad.log "info", "X-Copying #{k}..."
+        ncp src, dst


Use quotes for spec descriptions

Use quotes for spec descriptions

--- spec/operators-spec.coffee
+++ spec/operators-spec.coffee
@@ -381,7 +381,7 @@
       expect(vimState.getRegister('a').text).toBe "012 345\n"

   describe "the p keybinding", ->
-    describe 'character', ->
+    describe "character", ->
       beforeEach ->
         editor.getBuffer().setText "012\n"
         editor.setCursorScreenPosition [0, 0]
@@ -402,7 +402,7 @@
         expect(editor.getText()).toBe "0a12\n"
         expect(editor.getCursorScreenPosition()).toEqual [0, 2]

-    describe 'linewise', ->
+    describe "linewise", ->
       beforeEach ->
         editor.getBuffer().setText("012\n")
         editor.setCursorScreenPosition([0, 1])
@@ -428,7 +428,7 @@
         expect(editor.getText()).toBe "12345\nabcde\nABCDE\nQWERT"

   describe "the P keybinding", ->
-    describe 'character', ->
+    describe "character", ->
       beforeEach ->
         editor.getBuffer().setText("012\n")
         editor.setCursorScreenPosition([0, 0])
@@ -449,7 +449,7 @@
         expect(editor.getText()).toBe "a012\n"
         expect(editor.getCursorScreenPosition()).toEqual [0, 1]

-    describe 'linewise', ->
+    describe "linewise", ->
       beforeEach ->
         editor.getBuffer().setText("012\n")
         editor.setCursorScreenPosition([0, 1])


Skip that one for now.

Skip that one for now.

--- spec/side-view-spec.coffee
+++ spec/side-view-spec.coffee
@@ -19,7 +19,7 @@
     expect(view.hasClass 'top').toBe(true)
     expect(view.hasClass 'bottom').toBe(false)

-  it 'positions itself over the banner line', ->
+  xit 'positions itself over the banner line', ->
     refBanner = $(editorView).find('.line:contains("<<<<<<<")')
     expect(view.offset().top).toEqual(refBanner.offset().top)
     expect(view.height()).toEqual(refBanner.height())


Hide locale creation form on create

Hide locale creation form on create

--- app/controllers/project.coffee
+++ app/controllers/project.coffee
@@ -42,6 +42,7 @@
         @set 'currentLocale', newLocale
         @set 'model.translation.locales', locales
         @send 'changeLocale', newLocale
+        @set 'isNewLocaleVisible', false

     chooseType: (type) ->
       $('.project .types li').removeClass 'active'


Change logger name

Change logger name

--- app/src/m2fa/client.coffee
+++ app/src/m2fa/client.coffee
@@ -1,6 +1,6 @@
 @ledger.m2fa ?= {}

-DebugWebsocket = (message) -> ledger.utils.Logger.getLoggerByTag("M2FA_SOCKET").info message
+DebugWebsocket = (message) -> ledger.utils.Logger.getLoggerByTag("WebSocket").info message

 # The mobile 2FA ChromeClient.


fix cargo-cult bug

fix cargo-cult bug

--- js/inner/build.coffee
+++ js/inner/build.coffee
@@ -433,15 +433,13 @@
   toggle_artifacts: () =>
     if @artifacts_visible()
       @artifacts_visible(!@artifacts_visible())
-      @clean_artifacts()
       @artifacts(null)
     else
       @load_artifacts()
       @artifacts_visible(true)

   clean_artifacts: () =>
-    if @artifacts()
-      VM.cleanObjs(@artifacts())
+    @artifacts(null)

   load_artifacts: () =>
     $.ajax


#1160 Don't collect buckets if in requested state

--- app/assets/javascripts/oxalis/model/binary/bucket.coffee
+++ app/assets/javascripts/oxalis/model/binary/bucket.coffee
@@ -27,7 +27,7 @@

   shouldCollect : ->

-    collect = not @accessed and not @dirty
+    collect = not @accessed and not @dirty and @state != @STATE_REQUESTED
     @accessed = false
     return collect


#1643: Add unwrap method to Splat

#1643: Add unwrap method to Splat

--- src/nodes.coffee
+++ src/nodes.coffee
@@ -1009,6 +1009,7 @@
         unless obj.name.unwrapAll().isAssignable()
           throw SyntaxError "\"#{ obj.name.compile(o) }\" cannot be assigned."
         name = obj.name.unwrap().value
+        obj = obj.unwrap()
         val = "#{olen} <= #{vvar}.length ? #{ utility 'slice' }.call(#{vvar}, #{i}"
         if rest = olen - i - 1
           ivar = o.scope.freeVariable 'i'
@@ -1032,7 +1033,7 @@
       if name? and name in ['arguments','eval'].concat RESERVED
         throw new SyntaxError "assignment to a reserved word: #{obj.compile o} = #{val.compile o}"
       isSoak = (soakNode = obj.name or obj) and soakNode.base?.soak or soakNode.properties?[0]?.soak
-      assigns.push new Assign(obj.name or obj, val, null, param: @param).compile o, if isSoak then LEVEL_ACCESS else LEVEL_TOP
+      assigns.push new Assign(obj, val, null, param: @param).compile o, if isSoak then LEVEL_ACCESS else LEVEL_TOP
     assigns.push vvar unless top
     code = assigns.join ', '
     if o.level < LEVEL_LIST then code else "(#{code})"
@@ -1177,6 +1178,8 @@

   compile: (o) ->
     if @index? then @compileParam o else @name.compile o
+
+  unwrap: -> @name

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


Change module.exports and window.numbo to ES6 export

--- src/numbo.coffee
+++ src/numbo.coffee
@@ -282,8 +282,4 @@

 numbo = new Numbo()

-
-if module? and module.exports
-  module.exports = numbo
-if window?
-  window.numbo = numbo
+export default numbo


clear scheduled tasks list on fetch

clear scheduled tasks list on fetch

--- SingularityUI/app/controllers/RequestDetail.coffee
+++ SingularityUI/app/controllers/RequestDetail.coffee
@@ -123,6 +123,7 @@

         @collections.activeTasks.fetch().error    @ignore404
         @collections.scheduledTasks.fetch().error @ignore404
+        @collections.scheduledTasks.fetch({reset: true}).error @ignore404

         if @collections.requestHistory.currentPage is 1
             requestHistoryFetch = @collections.requestHistory.fetch()


renamed "Keyboard Preferences" to "Preferences"

--- client/ide.coffee
+++ client/ide.coffee
@@ -210,7 +210,7 @@
         ]}
     )
     {'': '_Edit', items: [
-      {'': '_Keyboard Preferences', action: prefs}
+      {'': '_Preferences', action: prefs}
       '-'
       {'': 'Weak Interrupt', action: WI}
     ]}


Fix comment links for when deployed to a non-root location

--- app/talk/comment-link.cjsx
+++ app/talk/comment-link.cjsx
@@ -52,7 +52,7 @@

           <PromiseRenderer promise={Promise.all [project, owner]}>{([project, owner]) =>
             projectCommentUrl =
-              window.location.origin +
+              window.location.origin + window.location.pathname +
               @makeHref 'project-talk-discussion',
                 {
                   board: discussion.board_id,
@@ -71,7 +71,7 @@

         else
           mainTalkCommentUrl =
-            window.location.origin +
+            window.location.origin + window.location.pathname +
             @makeHref 'talk-discussion',
               {board: discussion.board_id, discussion: discussion.id},
               {page: pageOfComment, comment: comment.id}


Improve details view back link criteria.

Since the service tree closes on service selection now,
the back link never points to the service tree.

It points to search results if they exist in
the app state.

--- src/views.coffee
+++ src/views.coffee
@@ -309,7 +309,7 @@
                 when 'details'
                     view = new DetailsView
                         model: @selected_units.first()
-                        back: @open_view_type
+                        search_results: @search_results
                 when 'event'
                     view = new EventView
                         model: @selected_events.first()
@@ -679,7 +679,7 @@
             @INITIAL_NUMBER_OF_EVENTS = 5
             @NUMBER_OF_EVENTS_FETCHED = 20
             @embedded = options.embedded
-            @back = options.back
+            @search_results = options.search_results

         render: ->
             super()
@@ -743,7 +743,7 @@

         user_close: (event) ->
             app.commands.execute 'clearSelectedUnit'
-            if @back == 'search'
+            unless @search_results.isEmpty()
                 app.commands.execute 'search'
             # TODO
             # else if @back == 'browse'
@@ -771,8 +771,8 @@
             data = @model.toJSON()
             data.provider = @get_translated_provider(@model.get('provider_type'))
             description = data.description
-            if @back?
-                data.back_to = i18n.t('sidebar.back_to.' + @back)
+            unless @search_results.isEmpty()
+                data.back_to = i18n.t('sidebar.back_to.search')
             MAX_LENGTH = 20
             if description
                 words = description.split /[ ]+/


Exclude VCS ignored paths in Atom

Exclude VCS ignored paths in Atom

--- atom/config.cson
+++ atom/config.cson
@@ -14,7 +14,6 @@
       "timecop"
       "vim-mode"
     ]
-    excludeVcsIgnoredPaths: false
     packagesWithKeymapsDisabled: [
       "emmet"
     ]


Use even tab sizing for Atom

Use even tab sizing for Atom

--- atom/config.cson
+++ atom/config.cson
@@ -107,6 +107,8 @@
   "markdown-preview":
     useGitHubStyle: true
   "merge-conflicts": {}
+  "one-dark-ui":
+    tabSizing: "Even"
   pigments:
     ignoredScopes: [
       "\\.comment"


Disable some Atom packages by default

Disable some Atom packages by default

--- atom/config.cson
+++ atom/config.cson
@@ -4,6 +4,8 @@
       "background-tips"
       "exception-reporting"
       "metrics"
+      "styleguide"
+      "timecop"
       "welcome"
     ]
   editor:


update atom config

update atom config

--- atom/config.cson
+++ atom/config.cson
@@ -1,27 +1,27 @@
 "*":
-  "exception-reporting":
-    userId: "42e7083d-4a9e-4628-29b7-09efaa2c29a3"
-  welcome:
-    showOnStartup: false
+  "atom-beautify":
+    _analyticsUserId: "83429072-4046-4dd1-85f5-5a216dc56d2a"
+  "atom-ternjs": {}
   core:
     themes: [
       "one-dark-ui"
       "one-dark-syntax"
     ]
   editor:
+    fontFamily: "Hack, Source Code Pro, monospace"
     invisibles: {}
-    fontFamily: "Source Code Pro"
     showIndentGuide: true
     showInvisibles: true
-  minimap:
-    plugins:
-      "git-diff": true
-      "find-and-replace": true
+  "exception-reporting":
+    userId: "42e7083d-4a9e-4628-29b7-09efaa2c29a3"
   "git-log":
     fontFamily: "Source Code Pro"
     fontScale: 1
-  "atom-ternjs": {}
-  "atom-beautify":
-    _analyticsUserId: "83429072-4046-4dd1-85f5-5a216dc56d2a"
   "keyboard-localization":
     useKeyboardLayout: "fr_CH"
+  minimap:
+    plugins:
+      "find-and-replace": true
+      "git-diff": true
+  welcome:
+    showOnStartup: false


:atom: Walk the walk

:atom: Walk the walk

--- atom/config.cson
+++ atom/config.cson
@@ -44,7 +44,7 @@
     packagesWithKeymapsDisabled: [
       "platformio-ide-terminal"
     ]
-    telemetryConsent: "no"
+    telemetryConsent: "limited"
     themes: [
       "nucleus-dark-ui"
       "one-dark-syntax"


Update atom config

Update atom config

--- atom/config.cson
+++ atom/config.cson
@@ -211,6 +211,7 @@
       fetchCommitTemplateOption: "File"
       fetchOption: "jf get"
       modalComparison: true
+      pullOption: "hg pull"
       showCommitInfoViewInline: false
       showTools: true
     "fb-java":
@@ -326,6 +327,9 @@
         pathToRefmt: "refmt"
         refmtFlags: ""
       pyls:
+        BuckFormatOnSave: false
+        DsfmtFormatOnSave: false
+        InstagramFormatOnSave: false
         formatAlreadyFormattedFilesOnSave: false
         plugins:
           fb_dataswarm_linter:


Add theme-switcher config

Add theme-switcher config

--- atom/config.cson
+++ atom/config.cson
@@ -49,6 +49,9 @@
   "spell-check": {}
   "status-bar": {}
   tabs: {}
+  "theme-switcher":
+    syntaxThemeOne: "one-dark-syntax"
+    syntaxThemeTwo: "one-light-syntax"
   "tree-view": {}
   welcome:
     showOnStartup: false


Hide Menu Bar

Hide Menu Bar

Former-commit-id: 3dc826d8c65abb42984231b3aa27e544f0a1bc00

--- atom/config.cson
+++ atom/config.cson
@@ -7,6 +7,7 @@
       "outlander-ui"
       "atom-material-syntax-dark"
     ]
+    autoHideMenuBar: true
   editor:
     fontFamily: "DejaVu Sans Mono"
     fontSize: 16


Wrap Markdown files at line length

Wrap Markdown files at line length

--- atom/config.cson
+++ atom/config.cson
@@ -63,6 +63,9 @@
     showOnStartup: false
   whitespace:
     ignoreWhitespaceOnCurrentLine: false
+".gfm.source":
+  editor:
+    softWrapAtPreferredLineLength: true
 "source.gfm":
   editor:
     softWrap: true


format fixes

format fixes

--- client/app/MainApp/status.coffee
+++ client/app/MainApp/status.coffee
@@ -26,7 +26,7 @@
       options = autoReconnect : options

     autoReconnect = options.autoReconnect
-    @reason = options.reason or undefined
+    @reason = options.reason

     @remote.disconnect(autoReconnect)
     @disconnected()
@@ -43,7 +43,7 @@
       @startPingingKites()

   startPingingKites: ->
-    @eachKite (channel) ->
+    @eachKite (channel)->
       channel.setStartPinging()

   disconnected: () ->
@@ -55,7 +55,7 @@
     @emit "disconnected", @reason

   stopPingingKites: ->
-    @eachKite (channel) ->
+    @eachKite (channel)->
       channel.setStopPinging()

   eachKite: (callback) ->


Latest Atom configuration

Latest Atom configuration

--- atom/config.cson
+++ atom/config.cson
@@ -73,7 +73,7 @@
     param_description: false
     return_description: false
   editor:
-    fontFamily: "MonoLisa"
+    fontFamily: "MonoLisa-Light"
     fontSize: 18
     invisibles:
       cr: "↩"
@@ -122,6 +122,7 @@
     locales: [
       "en-US"
     ]
+    useLocales: false
   "status-bar":
     cursorPositionFormat: "L:%L C:%C"
   tabs:


Remove deprecated Linter settings

Remove deprecated Linter settings

--- atom/config.cson
+++ atom/config.cson
@@ -62,11 +62,10 @@
     maxVisibleSuggestions: 5
   linter:
     lintOnFly: false
-    statusIconPosition: "Right"
     showErrorTabProject: false
     showErrorTabFile: false
     showErrorInline: false
-    defaultErrorTab: "Line"
     showErrorTabLine: true
   "line-ending-converter":
     showEolInStatusBar: false
+  "linter-shellcheck": {}


Fix SCSS config

Fix SCSS config

--- atom/config.cson
+++ atom/config.cson
@@ -58,6 +58,6 @@
 ".css.source":
   editor:
     tabLength: 2
-".sass.source":
+".scss.source":
   editor:
     tabLength: 4


fix minimap conf to atom

fix minimap conf to atom

--- atom/config.cson
+++ atom/config.cson
@@ -14,6 +14,8 @@
     invisibles: {}
   "exception-reporting":
     userId: "ca4ba82f-55d9-6783-63e8-7a2a1588920c"
+  minimap:
+    autoToggle: false
   "package-sync":
     forceOverwrite: true
   welcome:


EditorPane: Much better way to set editorMode, based on extension

--- client/CollaborativeWorkspace/panes/collaborativeeditorpane.coffee
+++ client/CollaborativeWorkspace/panes/collaborativeeditorpane.coffee
@@ -31,7 +31,7 @@
     @firepad.setText content  if @amIHost
     @codeMirrorEditor.scrollTo 0, 0
     @emit "OpenedAFile", file, content
-    @setEditorMode file
+    @setEditorMode file.getExtension()

   save: ->
     file        = @getData()
@@ -67,7 +67,7 @@
         "Ctrl-S"      : @bound "handleSave"

     @setEditorTheme()
-    @setEditorMode()
+    @setEditorMode 'html'

   handleSave: ->
     @save()
@@ -86,13 +86,11 @@
     document.head.appendChild link
     @codeMirrorEditor.setOption "theme", "ambiance"

-  setEditorMode:(file) ->
-    file or= @getOption 'file'
+  setEditorMode:(fileExtension) ->

-    return  unless file
+    return  unless fileExtension

     CodeMirror.modeURL = "#{cdnRoot}/mode/%N/%N.js"
-    fileExtension      = file.getExtension()
     syntaxHandler      = __aceSettings.syntaxAssociations[fileExtension]
     modeName           = null
     corrections        =
@@ -100,6 +98,7 @@
       json             : "javascript"
       js               : "javascript"
       go               : "go"
+      coffee           : "coffeescript"

     if corrections[fileExtension]
       modeName = corrections[fileExtension]


feed: remove logs

feed: remove logs

--- client/app/Applications/Feeder.kdapplication/FeedController.coffee
+++ client/app/Applications/Feeder.kdapplication/FeedController.coffee
@@ -195,8 +195,6 @@
       unless prefetchedItems = KD.prefetchedFeeds[feedId]
       then @loadFeed filter
       else
-        log "exhausting feed:", feedId
-        log "buyur dayi feed var, burdan yukle."
         kallback null, (KD.remote.revive item for item in prefetchedItems)
     else
-      filter.dataSource selector, options, kallback+      filter.dataSource selector, options, kallback


don't scroll to top when paging through exercises

--- tutor/src/components/exercises/details.cjsx
+++ tutor/src/components/exercises/details.cjsx
@@ -110,6 +110,7 @@
           isBackwardEnabled={moves.prev}
           onForwardNavigation={@onNext}
           onBackwardNavigation={@onPrev}
+          scrollOnNavigation={false}
         >
           <ExercisePreview
             className='exercise-card'


remove stray console.log

remove stray console.log

--- tutor/src/components/new-course/wizard.cjsx
+++ tutor/src/components/new-course/wizard.cjsx
@@ -68,7 +68,6 @@

   BackButton: ->
     return null if @state.currentStage is @state.firstStage
-    console.log @state.currentStage
     <BS.Button onClick={@onBack} className="back">
       Back
     </BS.Button>


return null when step is not found

return null when step is not found

--- tutor/src/components/task-plan/reading.cjsx
+++ tutor/src/components/task-plan/reading.cjsx
@@ -54,8 +54,9 @@

   render: ->
     { page } = @props
+    return null unless page
+
     actionButtons = @getActionButtons()
-

       <ChapterSection section={page.chapter_section.asString}/>
       {page.title}


Team Resources: remove usage of stateChanged listener, replace it with apply listener

--- client/admin/lib/views/resources/resourcelistitem.coffee
+++ client/admin/lib/views/resources/resourcelistitem.coffee
@@ -110,7 +110,6 @@

     { computeController } = kd.singletons
     computeController.on "apply-#{resource._id}", @bound 'handleProgressEvent'
-    computeController.on "stateChanged-#{resource._id}", @bound 'handleStateChangedEvent'

     @subscribeToKloudEvents()

@@ -130,7 +129,6 @@
     if state in ['Building', 'Destroying']
       { eventListener } = kd.singletons.computeController
       eventListener.addListener 'apply', resource._id
-      eventListener.addListener 'stateChanged', resource._id

   handleDestroy: ->
@@ -201,8 +199,7 @@
     @updatePercentage percentage
     @updateProgressBar percentage

-
-  handleStateChangedEvent: (event) ->
+    return  unless percentage is 100

     # delay is needed to show 100% in progress bar
     # when destroy process is completed
@@ -253,7 +250,6 @@
     resource              = @getData()
     { computeController } = kd.singletons
     computeController.off "apply-#{resource._id}", @bound 'handleProgressEvent'
-    computeController.off "stateChanged-#{resource._id}", @bound 'handleStateChangedEvent'

     super


map: Update copyright and fix a documentation issue.

--- client/map.coffee
+++ client/map.coffee
@@ -1,4 +1,4 @@
-# Copyright 2010-2012 Christopher Alfeld
+# Copyright 2010-2013 Christopher Alfeld
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -29,14 +29,14 @@
 #
 # The main advantage over a standard javascript object is that all accesses
 # pass through a function, allowing computation or side effects.  See, for
-# example, {Heron.ParametricMap}.
+# example, {Heron.Map.ParametricMap}.
 #
 # Any parameter in Heron that asks for a {Heron.Map} is duck typed and asking
 # for an object that defines {#get}, {#gets}, {#geta}, {#keys}, and, if it
 # needs a writable map, {#set}.
 #
 # @author Christopher Alfeld (calfeld@calfeld.net)
-# @copyright 2012 Christopher Alfeld
+# @copyright 2013 Christopher Alfeld
 class Heron.Map
   # Access keys as object.
   #


fix collision between plateform and balls

--- client/scripts/coffee/app.coffee
+++ client/scripts/coffee/app.coffee
@@ -62,7 +62,6 @@
       @generate_fake_player()

       @set_players_position()
-      console.log @plateforms

       #cursors = @phaser.input.keyboard.createCursorKeys()
       @balls = @phaser.add.group()
@@ -71,6 +70,9 @@
         star = @balls.create i * 70, 0, 'star'
         star.body.gravity.y = 6
         star.body.bounce.y = 0.7 + Math.random() * 0.2
+
+      console.log @plateforms.countLiving()
+      console.log @balls.countLiving()

       scoreText = @phaser.add.text 16, 16, 'score: 0', font: '32px arial', fill: '#000'

@@ -81,7 +83,7 @@
       scoreText.content = "Score: #{score}"

     update: () ->
-      @phaser.physics.collide @balls, @platforms
+      @phaser.physics.collide @balls, @plateforms
       #@phaser.physics.overlap @balls, @collectStar, null, this

 game = new Game()


fix error notifcation spec

fix error notifcation spec

--- coach/specs/concept-coach/error-notification.spec.coffee
+++ coach/specs/concept-coach/error-notification.spec.coffee
@@ -14,8 +14,9 @@
 getModal = (dom) -> dom.querySelector('.errors.modal')

 ERROR =
-  response: {status: 500, statusText: 'Its On Fire!'}
-  failedData:
+  response:
+    status: 500
+    statusText: 'Its On Fire!'
     data:
       errors: [ {code: 'test_test_test'} ]


Revert "msg wip"

Revert "msg wip"

This reverts commit 08652d711915b503931d50e406222a3062f44e11.


add bogus dimension to pics in coppermine conversion

--- conversion/coppermine.coffee
+++ conversion/coppermine.coffee
@@ -142,13 +142,13 @@
         media: [
           {
             src: "http://kuvat.aniki.fi/albums/#{copperminePicture.filepath}normal_#{copperminePicture.filename}",
-            width: 'TODO'
-            height: 'TODO'
+            width: 600 # TODO
+            height: 400 # TODO
           }
           {
             src: "http://kuvat.aniki.fi/albums/#{copperminePicture.filepath}#{copperminePicture.filename}",
-            width: 'TODO'
-            height: 'TODO'
+            width: 6000 # TODO
+            height: 4000 # TODO
             original: true
           }
         ]


Timeout added for assure trips

Timeout added for assure trips

--- src/tests/cucumber/features/step_definitions/uc3.coffee
+++ src/tests/cucumber/features/step_definitions/uc3.coffee
@@ -7,7 +7,7 @@
   For test preparation sophisticated function created which takes addresses
   turns then into location and saves the trip using carpoolService
   ###
-  @Given /^Assure "([^"]*)" trip:$/, (user, table)->
+  @Given /^Assure "([^"]*)" trip:$/, {timeout: 20 * 1000}, (user, table)->
     @TestHelper.login(user);
     #d "Table", table.hashes()
     for trip in table.hashes()
@@ -21,7 +21,7 @@
               carpoolService.saveTrip trip, (err)->
                 if err then done err else done trip
         , trip
-    #client.saveScreenshot('../build/screenshots/uc3-assureTrips.png')
+    #client.saveScreenshot('../build/screenshots/uc3-assureTrips.png')
     d "Result:",result

   @Given /^I see no trips filtered$/, ()->


Edit several Alumni Bios.

Edit several Alumni Bios.

--- app/pages/about/team-page.cjsx
+++ app/pages/about/team-page.cjsx
@@ -21,11 +21,6 @@
         bio: '''Adam is responsible for managing the Zooniverse's web hosting infrastructure.
         He has a computer science degree and has worked in web hosting and development for many years.
         He's also working on a degree in astronomy with the OU.'''
-      amyBoyer:
-        title: 'Developer'
-        bio: '''Amy has been a Zooniverse developer at the Adler since November 2015. She holds a BS and
-        MS in computer science and has over a decade of industry experience. An aspiring astronomer as
-        a child, she couldn't be happier to have landed here.'''
       alexBowyer:
         title: 'Web Science Architect'
         bio: '''Alex worked remotely for Zooniverse from his home in Northumberland.
@@ -40,12 +35,21 @@
         bio: '''Ali spent most of her PhD chasing lions around the Serengeti. She finished
         her PhD in Ecology, Evolution, and Behavior at the University of Minnesota in 2014,
         and joined the Zooniverse as a Postdoc in Ecology and Citizen Science.'''
+      # amitKapadia:
+      #   title: 'Developer'
+      #   bio: '''Amit was a developer at the Adler Planetarium.'''
+      amyBoyer:
+        title: 'Developer'
+        bio: '''Amy has been a Zooniverse developer at the Adler since November 2015. She holds a BS and
+        MS in computer science and has over a decade of industry experience. An aspiring astronomer as
+        a child, she couldn't be happier to have landed here.'''
       andreaSimenstad:
         title: 'Developer'
         bio: '''Andrea joined Zooniverse as a developer at the University of Minnesota in 2015. When she is not captivated by code, she can be found enjoying snow on skis and exploring lakes by kayak. She graduated from Carleton College with a degree in Cognitive Science.'''
       arfonSmith:
         title: 'Technical Lead'
-        bio: 'Arfon was co-founder and Technical Lead of the Zooniverse for the first five years of the project.'
+        bio: '''Arfon was co-founder and Technical Lead of the Zooniverse for the first five years of the project.
+        Arfon served as Director of Citizen Science at the Adler Planetarium and co-lead of the Zooniverse until fall 2013.'''
       beverleyNewing:
         title: 'Web Developer Intern'
         bio: '''Beverley is an English and German literature graduate, now working as a Web Developer Intern at Zooniverse. In her spare time she's an avid fan of geese and enjoys motorbiking.'''
@@ -84,17 +88,17 @@
         his PhD in astrophysics from Drexel University.'''
       darrenMcRoy:
         title: 'Community Manager'
-        bio: '''Darren (DZM) served as a liaison to the Zooniverse community and assists with strategic
+        bio: '''Darren (DZM) served as a liaison to the Zooniverse community and assisted with strategic
         content for projects. A Northwestern University graduate in journalism, he is also a golf addict,
         amateur author, and hopeless animal lover.'''
       darrylWright:
         title: 'Researcher'
         bio: 'Darryl is doing machine learning research with the Zooniverse, based in Oxford.'
-      edPaget:
-        title: 'Developer'
-        bio: '''Ed Paget joined Adler's Zooniverse Team as a software developer in August 2012.
-        He previously worked as a freelance programmer and photographer. Ed graduated from
-        Northwestern University with a major in Radio/TV/Film.'''
+      # edPaget:
+      #   title: 'Developer'
+      #   bio: '''Ed Paget joined Adler's Zooniverse Team as a software developer in August 2012.
+      #   He previously worked as a freelance programmer and photographer. Ed graduated from
+      #   Northwestern University with a major in Radio/TV/Film.'''
       grantMiller:
         title: 'Communications Lead'
         bio: '''A former exoplanetary scientist, Grant is now responsible for communicating with our volunteers and researchers. He is also behind the Zooniverse's presence on
@@ -109,9 +113,9 @@
         Greg loves to eat pancakes with real maple syrup and run ultramarathons.'''
       heathVanSingel:
         title: 'Designer'
-        bio: '''Heath was the UX/UI Designer for projects at the Zooniverse where he worked to craft
-        thoughtful and engaging user experiences. Apart from design Heath also enjoys illustration,
-        a good sci-fi or fantasy novel, and trips back to Michigan.'''
+        bio: '''Heath was the UX/UI Designer at the Zooniverse where he worked to craft thoughtful and
+        engaging user experiences. Apart from design, Heath also enjoys illustration, a good sci-fi or fantasy
+        novel, and exploring his new home in the Pacific Northwest.'''
       hughDickinson:
         title: 'Researcher'
         bio: '''Hugh is a postdoc studying the Zooniverse.'''
@@ -158,7 +162,7 @@
       lauraWhyte:
         title: 'Adler Planetarium Zooniverse Director'
         bio: '''Laura was the Director of Citizen Science at Adler Planetarium and co-lead of the
-        Zooniverse until 2015.'''
+        Zooniverse from fall 2013 to summer 2015.'''
       lucyFortson:
         title: 'University of Minnesota Zooniverse Director'
         bio: '''Associate Professor of Physics and Astronomy at the University of Minnesota. One of the Zooniverse
@@ -234,9 +238,9 @@
         from Bristol and loves cooking.'''
       stuartLynn:
         title: 'Developer'
-        bio: '''Stuart arrived at the Adler as a developer in July 2011, having been in our Oxford team previously.
-        He studied mathematical physics at Edinburgh University and received his doctorate in astrophysics from Royal
-        Observatory Edinburgh. Data analysis maestro.'''
+        bio: '''Stuart arrived at the Adler as a developer in July 2011, having been one of the founding
+        developers at Oxford. He studied mathematical physics at Edinburgh University and received his doctorate
+        in astrophysics from Royal Observatory Edinburgh. Data analysis maestro and web developer.'''
       steveRaden:
         title: 'Developer'
         bio: 'Steve Raden was a developer at the Adler in Chicago.'
@@ -273,6 +277,12 @@
     bio: counterpart "team.content.alexBowyer.bio"
     image: "/assets/team/alex_bowyer.jpg"
     location: "alumni"
+  # amitKapadia:
+  #   name: "Amit Kapadia"
+  #   title: counterpart "team.content.amitKapadia.title"
+  #   bio: counterpart "team.content.amitKapadia.bio"
+  #   image: "/assets/team/amit.jpg"
+  #   location: "alumni"
   amyBoyer:
     name: "Amy Boyer"
     title: counterpart "team.content.amyBoyer.title"
@@ -370,13 +380,13 @@
     bio: counterpart "team.content.darrylWright.bio"
     image: "/assets/team/team-member-avatar.jpg"
     location: "oxford"
-  edPaget:
-    name: "Ed Paget"
-    twitter: "edpaget"
-    title: counterpart "team.content.edPaget.title"
-    bio: counterpart "team.content.edPaget.bio"
-    image: "/assets/team/ed.png"
-    location: "alumni"
+  # edPaget:
+  #   name: "Ed Paget"
+  #   twitter: "edpaget"
+  #   title: counterpart "team.content.edPaget.title"
+  #   bio: counterpart "team.content.edPaget.bio"
+  #   image: "/assets/team/ed.png"
+  #   location: "alumni"
   grantMiller:
     name: "Grant Robert MacKinnon Miller"
     twitter: "mrniaboc"


Cleaner commenting out

Cleaner commenting out

--- app/scripts/scenes/City.coffee
+++ app/scripts/scenes/City.coffee
@@ -12,19 +12,19 @@
     namespace: 'City'
     startScenery: 'Intro'

-  #level.addBlock 'City.Intro',
-    #fast: yes
-    #enter: ->
-      #text = "Stage #{@level.data.stage}: #{@level.data.title}"
-      #Crafty.e('StageTitle').stageTitle(text)
-
-      #@level.showDialog([
-        #'p1,p2:John:Too bad we have to bring these ships to the museum!'
-        #'p1,!p2:John:Too bad we have to bring this ship to the museum!'
-        #':General:Just give her a good last flight,\nwe document some moves on the way!'
-      #])
-
   ###
+  level.addBlock 'City.Intro',
+    fast: yes
+    enter: ->
+      text = "Stage #{@level.data.stage}: #{@level.data.title}"
+      Crafty.e('StageTitle').stageTitle(text)
+
+      @level.showDialog([
+        'p1,p2:John:Too bad we have to bring these ships to the museum!'
+        'p1,!p2:John:Too bad we have to bring this ship to the museum!'
+        ':General:Just give her a good last flight,\nwe document some moves on the way!'
+      ])
+
   level.addBlock 'City.Ocean'

   level.addBlock 'City.Ocean',


uncomment the event handling code and incorporate it into a new function

--- cf/spell.coffee
+++ cf/spell.coffee
@@ -25,16 +25,17 @@

   update_question = (data) ->
     word = random_question(data)
-    $("#input_answer").keydown((e) =>
-      console.log @current
-      )
+    update_keyHandler()
     $("p#hint").empty().append(word['hint'])

-#  $("#input_answer").keydown((e) =>
-#    console.log this.current
-#    if e.which == 13
-#      attempt = $("#input_answer").val()
-#      if attempt.toLowerCase() == @choice.toLowerCase()
-#        alert("SUCCESS!")
-#      else
-#        alert("FAIL!")
+
+  update_keyHandler = () ->
+    $("#input_answer").keydown((e) =>
+      console.log this.current
+      if e.which == 13
+        attempt = $("#input_answer").val()
+        if attempt.toLowerCase() == @choice.toLowerCase()
+          alert("SUCCESS!")
+        else
+          alert("FAIL!")
+    )


CREATE-1383 Remove default noop and check if sanitiseHtml is a function in private helper

--- src/quill.coffee
+++ src/quill.coffee
@@ -28,8 +28,7 @@
     styles: {}
     theme: 'base',
     onFocus: false,
-    onBlur: false,
-    sanitiseHtml: _.noop
+    onBlur: false

   @events:
     FORMAT_INIT      : 'format-init'
@@ -231,7 +230,11 @@
   updateContents: (delta, source = Quill.sources.API) ->
     delta = { ops: delta } if Array.isArray(delta)
     @editor.applyDelta(delta, source)
-    @root.innerHTML = @options.sanitiseHtml(@root.innerHTML)
+    this._sanitiseHtml()
+
+  _sanitiseHtml: () ->
+    if (_.isFunction(@options.sanitiseHtml))
+      @root.innerHTML = @options.sanitiseHtml(@root.innerHTML)

   # fn(Number start, Number end, String name, String value, String source)
   # fn(Number start, Number end, Object formats, String source)


Oops. Guard by auth role.

Oops. Guard by auth role.

--- src/quote.coffee
+++ src/quote.coffee
@@ -140,6 +140,13 @@
           msg.send "#{quotes.length} quotes reloaded successfully."

   robot.respond /buffer quote/i, (msg) ->
+    unless robot.auth.hasRole(msg.message.user, CREATOR_ROLE)
+      msg.reply [
+        "You can't do that! You're not a *#{CREATOR_ROLE}*."
+        "Ask an admin to run `#{robot.name} #{msg.message.user.name} has #{CREATOR_ROLE} role`."
+      ].join("\n")
+      return
+
     unless robot.bufferForUserName?
       msg.reply "Buffer package not available."
       return


Update Range::intersectsWith docs

Update Range::intersectsWith docs

--- src/range.coffee
+++ src/range.coffee
@@ -154,6 +154,10 @@
   # Public: Determines whether this range intersects with the argument.
   #
   # * `otherRange` A {Range} or range-compatible {Array}
+  # * `exclusive` (optional) {Boolean} indicating whether to exclude endpoints
+  #     when testing for intersection. Defaults to `false`.
+  #
+  # Returns a {Boolean}.
   intersectsWith: (otherRange, exclusive) ->
     if exclusive
       not (@end.isLessThanOrEqual(otherRange.start) or @start.isGreaterThanOrEqual(otherRange.end))


Accessibility: fix slider movement

Accessibility: fix slider movement

--- @client/slider.coffee
+++ @client/slider.coffee
@@ -212,6 +212,7 @@
       tabIndex: 0

       onKeyDown: (e) =>
+        console.log 'GOT KEY', e.which
         if e.which in [37, 38, 39, 40, 33, 34, 35, 36]
           amount =  if e.which in [37, 38, 39, 40]
                       .05
@@ -225,10 +226,14 @@
                       else
                         1

-          slider.value += direction * amount
-          slider.value = Math.max slider.value, (if @props.polarized then -1 else 0)
-          slider.value = Math.min slider.value, 1
-          save slider
+          new_val = slider.value
+          new_val += direction * amount
+          new_val = Math.max new_val, (if @props.polarized then -1 else 0)
+          new_val = Math.min new_val, 1
+          if new_val != slider.value
+            slider.value = new_val
+            save slider
+            @props.onMouseUpCallback?(e)
           e.preventDefault()
         else if e.which == 13 || e.which == 32 # ENTER or SPACE
           @props.onMouseUpCallback(e) if @props.onMouseUpCallback


Fixing the manual listener

Fixing the manual listener

--- scripts/jira-linkifier.coffee
+++ scripts/jira-linkifier.coffee
@@ -31,7 +31,7 @@

     msg.send "Watching that project for you"

-  robot.hear ("MOBILE-([0-9]*)", "i"), (mention) ->
+  robot.hear /MOBILE-([0-9]*)/i, (mention) ->
     msg.send("Issue at: https://jira.brandnetworksinc.com/browse/" + "MOBILE" + "-" + mention.match[1])

   robot.respond /stop watching ?(.+)?/i, (msg) ->


remove forced

remove forced

--- lib/generator/app/client/javascripts/models/home.js.coffee
+++ lib/generator/app/client/javascripts/models/home.js.coffee
@@ -1,5 +1,3 @@
 Nali.Model.extend Home:

   attributes: {}
-
-  forced: true


Remove leading slash from keep-alive request path

`HEROKU_URL` always has a trailing slash, this fixes a double slash
issue.

--- src/robot.coffee
+++ src/robot.coffee
@@ -198,7 +198,7 @@

     if hostname
       setInterval =>
-        HttpClient.create("#{hostname}/hubot/ping")
+        HttpClient.create("#{hostname}hubot/ping")
           .post() (err, res, body) =>
             @logger.info "keep alive ping!"
       , 1200000


Superify the help output

Superify the help output

You should now document `robot.respond` commands with hubot at the beginning.
This will get replaced in the help output so the correct name of the running
robot is displayed.

--- src/robot.coffee
+++ src/robot.coffee
@@ -244,9 +244,9 @@
     Fs.readFile path, 'utf-8', (err, body) =>
       throw err if err?
       for i, line of body.split("\n")
-        break    if !(line[0] == '#' or line.substr(0, 2) == '//')
-        continue if !line.match('-')
-        @commands.push line[2..line.length]
+        break    if not (line[0] == '#' or line.substr(0, 2) == '//')
+        continue if not line.match('-')
+        @commands.push line[2..line.length].replace /^hubot/i, @name

   # Public: A helper send function which delegates to the adapter's send
   # function.


delta fix

delta fix

--- coffee/app.coffee
+++ coffee/app.coffee
@@ -74,6 +74,9 @@
 		yDelta = (tiltAngle * 0.5 / 180 * yStart + tiltAngle * 0.5 / 180 * yEnd)
 		zoom = document.getElementById("minZoom").value

+		if yDelta < 0
+			yDelta *= -1
+
 		$http({
 			method: 'POST',
 			url: '/recorderjob',


missing comma

missing comma

--- scripts/meme_generator.coffee
+++ scripts/meme_generator.coffee
@@ -66,8 +66,8 @@
 	  memeGenerator msg, 79, 108785, msg.match[1], msg.match[2], (url) ->
       msg.send url

-  robot.respond /hipster (hacker|dev) (.*)/i (msg) ->
-    memeGenerator msg, 350369, 2083681, "hipster " + msg.match[1], msg.match[2], (url) ->
+  robot.respond /hipster (hacker|dev) (.*)/i, (msg) ->
+    memeGenerator msg, 350369, 2083681, "hipster #{msg.match[1]}", msg.match[2], (url) ->
       msg.send url

 memeGenerator = (msg, generatorID, imageID, text0, text1, callback) ->


[LoadingFree] change some function a little bit to accept 4 parameters

--- workers/social/lib/social/models/tag.coffee
+++ workers/social/lib/social/models/tag.coffee
@@ -276,10 +276,11 @@
       uniqueSelector.group = makeGroupSelector client.context.group
       @one uniqueSelector, options, callback

-  @some$ = permit 'read tags',
-    success:(client, selector, options, callback)->
-      selector.group = makeGroupSelector client.context.group
-      @some selector, options, callback
+  @_some = (client, selector, options, callback)->
+    selector.group = makeGroupSelector client.context.group
+    @some selector, options, callback
+
+  @some$ = permit 'read tags', success: @_some

   # fix: having read activity permission here may lead to obscurity - SY
   @fetchCount = permit 'read activity',


some more tests for lm vs lm

some more tests for lm vs lm

--- test/types/json.coffee
+++ test/types/json.coffee
@@ -375,19 +375,13 @@

 	'lm vs lm': (test) ->
 		test.deepEqual [{p:[0],lm:2}], type.transform [{p:[0],lm:2}], [{p:[2],lm:1}], 'client'
-		#test.deepEqual [{p:[1],lm:1}], type.transform [{p:[0],lm:0}], [{p:[5],lm:0}], 'client'
 		test.deepEqual [{p:[4],lm:4}], type.transform [{p:[3],lm:3}], [{p:[5],lm:0}], 'client'
 		test.deepEqual [{p:[2],lm:0}], type.transform [{p:[2],lm:0}], [{p:[1],lm:0}], 'client'
-
 		test.deepEqual [{p:[2],lm:1}], type.transform [{p:[2],lm:0}], [{p:[1],lm:0}], 'server'
-		#test.deepEqual [{p:[1],lm:1}], type.transform [{p:[0],lm:0}], [{p:[5],lm:0}], 'server'
 		test.deepEqual [{p:[3],lm:1}], type.transform [{p:[2],lm:0}], [{p:[5],lm:0}], 'server'
-
 		test.deepEqual [{p:[3],lm:0}], type.transform [{p:[2],lm:0}], [{p:[5],lm:0}], 'client'
-
 		test.deepEqual [{p:[0],lm:5}], type.transform [{p:[2],lm:5}], [{p:[2],lm:0}], 'client'
 		test.deepEqual [{p:[0],lm:5}], type.transform [{p:[2],lm:5}], [{p:[2],lm:0}], 'client'
-
 		test.deepEqual [{p:[0],lm:0}], type.transform [{p:[1],lm:0}], [{p:[0],lm:5}], 'server'
 		test.deepEqual [{p:[0],lm:0}], type.transform [{p:[1],lm:0}], [{p:[0],lm:1}], 'server'
 		test.deepEqual [{p:[1],lm:1}], type.transform [{p:[0],lm:1}], [{p:[1],lm:0}], 'client'
@@ -395,11 +389,16 @@
 		test.deepEqual [{p:[3],lm:2}], type.transform [{p:[2],lm:1}], [{p:[5],lm:0}], 'server'
 		test.deepEqual [{p:[2],lm:1}], type.transform [{p:[3],lm:1}], [{p:[1],lm:3}], 'client'
 		test.deepEqual [{p:[2],lm:3}], type.transform [{p:[1],lm:3}], [{p:[3],lm:1}], 'client'
-
 		test.deepEqual [{p:[2],lm:6}], type.transform [{p:[2],lm:6}], [{p:[0],lm:1}], 'client'
 		test.deepEqual [{p:[2],lm:6}], type.transform [{p:[2],lm:6}], [{p:[0],lm:1}], 'server'
 		test.deepEqual [{p:[2],lm:6}], type.transform [{p:[2],lm:6}], [{p:[1],lm:0}], 'client'
 		test.deepEqual [{p:[2],lm:6}], type.transform [{p:[2],lm:6}], [{p:[1],lm:0}], 'server'
+		test.deepEqual [{p:[0],lm:2}], type.transform [{p:[0],lm:1}], [{p:[2],lm:1}], 'client'
+		test.deepEqual [{p:[2],lm:0}], type.transform [{p:[2],lm:1}], [{p:[0],lm:1}], 'server'
+		test.deepEqual [{p:[1],lm:1}], type.transform [{p:[0],lm:0}], [{p:[1],lm:0}], 'client'
+		test.deepEqual [{p:[0],lm:0}], type.transform [{p:[0],lm:1}], [{p:[1],lm:3}], 'client'
+		test.deepEqual [{p:[3],lm:1}], type.transform [{p:[2],lm:1}], [{p:[3],lm:2}], 'client'
+		test.deepEqual [{p:[3],lm:3}], type.transform [{p:[3],lm:2}], [{p:[2],lm:1}], 'client'
 		test.done()

 	'indices change correctly around a move': (test) ->


check version before it is modified by applyOp

--- services/document-updater/app/coffee/ShareJsUpdateManager.coffee
+++ services/document-updater/app/coffee/ShareJsUpdateManager.coffee
@@ -23,7 +23,8 @@
 	applyUpdate: (project_id, doc_id, update, lines, version, callback = (error, updatedDocLines) ->) ->
 		logger.log project_id: project_id, doc_id: doc_id, update: update, "applying sharejs updates"
 		jobs = []
-
+		# record the update version before it is modified
+		incomingUpdateVersion = update.version
 		# We could use a global model for all docs, but we're hitting issues with the
 		# internal state of ShareJS not being accessible for clearing caches, and
 		# getting stuck due to queued callbacks (line 260 of sharejs/server/model.coffee)
@@ -44,7 +45,7 @@
 			model.getSnapshot doc_key, (error, data) =>
 				return callback(error) if error?
 				# only check hash when present and no other updates have been applied
-				if update.hash? and update.v == version
+				if update.hash? and incomingUpdateVersion == version
 					ourHash = ShareJsUpdateManager._computeHash(data.snapshot)
 					return callback(new Error("Invalid hash")) if ourHash != update.hash
 				docLines = data.snapshot.split(/\r\n|\n|\r/)


Force return true on a jquery handler; returning falsy values may have unexpected results.

--- services/web/public/coffee/ide/pdfng/directives/pdfViewer.coffee
+++ services/web/public/coffee/ide/pdfng/directives/pdfViewer.coffee
@@ -432,6 +432,7 @@
 						# if we still have a selection we need to keep the handler going
 						if not removedClass then _setMouseUpHandler()
 					, 10
+					return true

 				_removeClassIfNoSelection = () ->
 					if _hasSelection()


Social: add checkChannelParticipation function

--- workers/social/lib/social/models/socialapi/channel.coffee
+++ workers/social/lib/social/models/socialapi/channel.coffee
@@ -109,6 +109,11 @@

     @constructor.cycleChannel options, callback

+  @checkChannelParticipation = secureRequest
+    fnName   : 'checkChannelParticipation'
+    validate : ['name', 'type']
+
+
   # byId - fetch channel by id
   @byId = secureRequest
     fnName  : 'channelById'


Disable loading after no more results

Disable loading after no more results

--- apps/user/profile/index.coffee
+++ apps/user/profile/index.coffee
@@ -33,9 +33,12 @@
       success: (response) =>
         @page++
         @loading = false
-
-        $('.profile').append template
-          channels: response.channels
+
+        if response.channels.length
+          $('.profile').append template
+            channels: response.channels
+        else
+          @disabled = true

 module.exports.init = ->
   new ProfileView


muplti elements selection support

muplti elements selection support

--- app/scripts/editor.coffee
+++ app/scripts/editor.coffee
@@ -54,8 +54,9 @@
 		$('#debug').text $('#editor').html()
 	toggleFormatBlock: (tag)->
 		el = @getSelectedElement()
+		console.log el
 		if el.attr 'name'
-				@saveSelection()
+			@saveSelection()
 			if el.is tag
 				newEl = $('<p>').html el.html()
 				el.after(newEl)
@@ -66,6 +67,8 @@
 				el.after(newEl)
 				el.remove()
 				@restoreSelection()
+		else
+			document.execCommand('formatBlock', false, tag)
 	handleKeyDown: (e)->
 		# debug
 		$('#debug-keydown').text e.keyCode


Added some comments.

Added some comments.

--- app/assets/javascripts/app/lib/app_post/auth.js.coffee
+++ app/assets/javascripts/app/lib/app_post/auth.js.coffee
@@ -14,6 +14,7 @@
         # clear store
         App.Store.clear('all')

+        # set login (config, session, ...)
         @_login(data)

         # execute callback
@@ -32,6 +33,8 @@
       type:  'GET'
       url:   '/signshow'
       success: (data, status, xhr) =>
+
+        # set login (config, session, ...)
         @_login(data)

       error: (xhr, statusText, error) =>
@@ -45,6 +48,8 @@
       type: 'DELETE'
       url:  '/signout'
       success: =>
+
+        # set logout (config, session, ...)
         @_logout()

       error: (xhr, statusText, error) =>


Use https for ouroboros API

Use https for ouroboros API

--- app/index.coffee
+++ app/index.coffee
@@ -12,7 +12,7 @@
 Recent = require 'zooniverse/models/recent'

 api = if window.location.hostname is 'spacewarps.org'
-  new Api project: 'spacewarp', host: 'http://spacewarps.org', path: '/_ouroboros_api/proxy'
+  new Api project: 'spacewarp', host: 'https://spacewarps.org', path: '/_ouroboros_api/proxy'
 else
   new Api project: 'spacewarp'


Allow touch devices to dismiss flash bar

--- app/assets/javascripts/components/flash_bar.coffee
+++ app/assets/javascripts/components/flash_bar.coffee
@@ -1,4 +1,4 @@
 $(document)
-  .on("click", ".flash-bar-show", ->
+  .on("click touchstart", ".flash-bar-show", ->
     $(this).removeClass("flash-bar-show")
   )


Fix markdown input value for existing object.

--- app/assets/javascripts/formagic/inputs/markdown.coffee
+++ app/assets/javascripts/formagic/inputs/markdown.coffee
@@ -33,6 +33,8 @@
   _add_input: ->
     if @config.htmlFieldName
       @$inputHtml =$ "<input type='hidden' name='[#{ @config.htmlFieldName }]' />"
+      if @object
+        @$inputHtml.val(@object[@config.htmlFieldName])
       @$el.append @$inputHtml

     @$input =$ "<input type='hidden' name='#{ @name }' value='#{ @_safe_value() }' />"


OX-685 fix floor/round issue

OX-685 fix floor/round issue

--- app/assets/javascripts/oxalis/model/volumelayer.coffee
+++ app/assets/javascripts/oxalis/model/volumelayer.coffee
@@ -53,7 +53,7 @@

     setMap = (x, y) ->

-      x = Math.round(x); y = Math.round(y)
+      x = Math.floor(x); y = Math.floor(y)
       map[x - minCoord2d[0]][y - minCoord2d[1]] = true


Redirect / to /event_searches/new.

Redirect / to /event_searches/new.

/event_searches/new is the only implemented page at the moment, so we
don't need to provide e.g. an index of places to go.

--- app/assets/javascripts/routes/application_route.coffee
+++ app/assets/javascripts/routes/application_route.coffee
@@ -14,4 +14,7 @@
       editable: false
       available: Pancakes.StudyLocation.find()

+  redirect: ->
+    @transitionTo 'event_searches.new'
+
 # vim:ts=2:sw=2:et:tw=78