Remove afterEach duplication

Remove afterEach duplication

--- tests/request.spec.coffee
+++ tests/request.spec.coffee
@@ -9,6 +9,9 @@
 	beforeEach ->
 		token.remove()

+	afterEach ->
+		fetchMock.restore()
+
 	describe '.send()', ->

 		describe 'given a simple GET endpoint', ->
@@ -19,9 +22,6 @@
 					headers:
 						'Content-Type': 'application/json'

-			afterEach ->
-				fetchMock.restore()
-
 			describe 'given an absolute url', ->

 				beforeEach ->
@@ -71,9 +71,6 @@
 					body: method: 'DELETE'
 					headers:
 						'Content-Type': 'application/json'
-
-			afterEach ->
-				fetchMock.restore()

 			it 'should default to GET', ->
 				promise = request.send
@@ -86,9 +83,6 @@

 			beforeEach ->
 				fetchMock.get('https://api.resin.io/foo', 'Hello World')
-
-			afterEach ->
-				fetchMock.restore()

 			it 'should resolve with the plain body', ->
 				promise = request.send
@@ -103,9 +97,6 @@
 			beforeEach ->
 				fetchMock.post 'https://api.resin.io/foo', (url, opts) ->
 					return "The body is: #{opts.body}"
-
-			afterEach ->
-				fetchMock.restore()

 			it 'should take the plain body successfully', ->
 				promise = request.send
@@ -129,9 +120,6 @@
 							headers:
 								'Content-Type': 'application/json'

-					afterEach ->
-						fetchMock.restore()
-
 					it 'should correctly make the request', ->
 						promise = request.send
 							method: 'GET'
@@ -149,9 +137,6 @@
 							headers:
 								'Content-Type': 'application/json'

-					afterEach ->
-						fetchMock.restore()
-
 					it 'should be rejected with the error message', ->
 						promise = request.send
 							method: 'GET'
@@ -174,9 +159,6 @@
 					beforeEach ->
 						fetchMock.head('https://api.resin.io/foo', 200)

-					afterEach ->
-						fetchMock.restore()
-
 					it 'should correctly make the request', ->
 						promise = request.send
 							method: 'HEAD'
@@ -190,9 +172,6 @@
 					beforeEach ->
 						fetchMock.head('https://api.resin.io/foo', 500)

-					afterEach ->
-						fetchMock.restore()
-
 					it 'should be rejected with a generic error message', ->
 						promise = request.send
 							method: 'HEAD'
@@ -211,9 +190,6 @@
 						headers:
 							'Content-Type': 'application/json'

-				afterEach ->
-					fetchMock.restore()
-
 				it 'should eventually return the body', ->
 					promise = request.send
 						method: 'POST'
@@ -232,9 +208,6 @@
 						headers:
 							'Content-Type': 'application/json'

-				afterEach ->
-					fetchMock.restore()
-
 				it 'should eventually return the body', ->
 					promise = request.send
 						method: 'PUT'
@@ -253,9 +226,6 @@
 						headers:
 							'Content-Type': 'application/json'

-				afterEach ->
-					fetchMock.restore()
-
 				it 'should eventually return the body', ->
 					promise = request.send
 						method: 'PATCH'
@@ -274,9 +244,6 @@
 						headers:
 							'Content-Type': 'application/json'

-				afterEach ->
-					fetchMock.restore()
-
 				it 'should eventually return the body', ->
 					promise = request.send
 						method: 'DELETE'


Don't send brackets for action

Don't send brackets for action

--- components/SetAction.coffee
+++ components/SetAction.coffee
@@ -16,12 +16,9 @@
     forwardGroups: false
     async: true
   , (data, groups, out, callback) ->
-    actionParts = c.params.action.split ':'
     setTimeout ->
-      out.beginGroup part for part in actionParts
       out.send
         action: c.params.action
         payload: data
-      out.endGroup() for part in actionParts
       callback()
     , 1


Added undefined/null/boolean iteration for target check

--- tests/specs/inject.coffee
+++ tests/specs/inject.coffee
@@ -48,6 +48,38 @@
           ).throw TypeError, errMsg
       describe "Array, case, but it has non-string values", ->
         errMsg2 = "Target contains non-string param(s)"
+        describe "Undefined case", ->
+          expect(->
+            mkfunc [
+              undefined
+              "test2"
+              (t, t2) -> [t, t2]
+            ], secArg
+          ).throw TypeError, errMsg2
+        describe "null case", ->
+          expect(->
+            mkfunc [
+              "test"
+              null
+              (t, t2) -> [t, t2]
+            ], secArg
+          ).throw TypeError, errMsg2
+        describe "true case", ->
+          expect(->
+            mkfunc [
+              "test"
+              true
+              (t, t2) -> [t, t2]
+            ], secArg
+          ).throw TypeError, errMsg2
+        describe "false case", ->
+          expect(->
+            mkfunc [
+              false
+              "test2"
+              (t, t2) -> [t, t2]
+            ], secArg
+          ).throw TypeError, errMsg2
         describe "Number case", ->
           it "Should throw TypeError", ->
             expect(->


Fix historical tasks table in refactor_request

--- SingularityUI/app/views/requestHistoricalTasksTable.coffee
+++ SingularityUI/app/views/requestHistoricalTasksTable.coffee
@@ -58,10 +58,13 @@
                         cell: '{{#getShortTaskIDMiddleEllipsis name}}{{/getShortTaskIDMiddleEllipsis}}	'
                     ,
                         header: 'Status	'
-                        cell: '{{ lastStatusHuman }}	'
+                        cell: '{{ lastTaskStateHuman }}	'
                     ,
-                        header: 'Created	'
-                        cell: '{{ createdAtHuman }}	'
+                        header: 'Deploy ID	'
+                        cell: '{{ deployId }}	'
+                    ,
+                        header: 'Started	'
+                        cell: '{{ startedAtHuman }}	'
                     ,
                         header: 'Updated	'
                         cell: '{{ updatedAtHuman }}	'


Tests: Temporarily disable openFile test.

--- tests/src/ide/file.coffee
+++ tests/src/ide/file.coffee
@@ -7,28 +7,28 @@
 module.exports =

-  openFile: (browser) ->
+  # openFile: (browser) ->

-    user = helpers.beginTest(browser)
-    helpers.waitForVMRunning(browser)
+  #   user = helpers.beginTest(browser)
+  #   helpers.waitForVMRunning(browser)

-    helpers.openFolderContextMenu(browser, user, 'Web')
+  #   helpers.openFolderContextMenu(browser, user, 'Web')

-    webPath       = '/home/' + user.username + '/Web'
-    indexSelector = "span[title='" + webPath + '/index.html' + "']"
+  #   webPath       = '/home/' + user.username + '/Web'
+  #   indexSelector = "span[title='" + webPath + '/index.html' + "']"

-    browser
-      .waitForElementVisible   'li.expand', 15000
-      .click                   'li.expand'
-      .waitForElementVisible   indexSelector, 15000
-      .click                   indexSelector
-      .click                   indexSelector + ' + .chevron'
-      .waitForElementVisible   'li.open-file', 20000
-      .click                   'li.open-file'
-      .waitForElementVisible   '.indexhtml',   20000 # Assertion
-      .waitForElementVisible   '.ace_content', 20000 # Assertion
-      .assert.containsText     '.ace_content', 'Hello World from HTML by Koding' # Assertion
-      .end()
+  #   browser
+  #     .waitForElementVisible   'li.expand', 15000
+  #     .click                   'li.expand'
+  #     .waitForElementVisible   indexSelector, 15000
+  #     .click                   indexSelector
+  #     .click                   indexSelector + ' + .chevron'
+  #     .waitForElementVisible   'li.open-file', 20000
+  #     .click                   'li.open-file'
+  #     .waitForElementVisible   '.indexhtml',   20000 # Assertion
+  #     .waitForElementVisible   '.ace_content', 20000 # Assertion
+  #     .assert.containsText     '.ace_content', 'Hello World from HTML by Koding' # Assertion
+  #     .end()

   createFile: (browser) ->


Use @ instead of this for consistency

Use @ instead of this for consistency

--- src/jquery-extensions.coffee
+++ src/jquery-extensions.coffee
@@ -92,7 +92,7 @@
 # event name isn't specified or an array of event handlers if an event name is
 # specified. This method never returns null or undefined.
 $.fn.handlers = (eventName) ->
-  handlers = if this.length then $._data(this[0], 'events') ? {} else {}
+  handlers = if @length then $._data(@[0], 'events') ? {} else {}
   handlers = handlers[eventName] ? [] if arguments.length is 1
   handlers


Clean polling/tree code remnants

Clean polling/tree code remnants

* Update nodes and handle changes and errors
* WIN


flux/teams: modify store to hold user can edit Team

--- client/app/lib/flux/teams/stores/teaminvitationinputvaluesstore.coffee
+++ client/app/lib/flux/teams/stores/teaminvitationinputvaluesstore.coffee
@@ -12,18 +12,18 @@
     @on actions.RESET_TEAM_INVITES, @handleReset # handleReset comes from base class

-  createRecord = (role) ->
+  createRecord = (canEdit) ->
     immutable.Map
       email: ''
       firstName: ''
       lastName: ''
-      role: role
+      canEdit: canEdit

   getInitialState: ->

-    ['member', 'member', 'member'].reduce (state, role, index) ->
-      state.set index, createRecord role
+    [no, no, no].reduce (state, canEdit, index) ->
+      state.set index, createRecord canEdit
     , immutable.Map()

@@ -55,5 +55,5 @@

     return state  if empties.size

-    return state.set(state.size, createRecord 'member')
+    return state.set(state.size, createRecord no)


home/billing: add support for updating credit card

--- client/home/lib/billing/components/paymentinformation/container.coffee
+++ client/home/lib/billing/components/paymentinformation/container.coffee
@@ -36,7 +36,11 @@

     { formValues } = @state
     { reactor } = kd.singletons
-    { createStripeToken, subscribeGroupPlan, loadGroupCreditCard } = PaymentFlux(reactor).actions
+    { actions, getters } = PaymentFlux reactor
+
+    { createStripeToken, subscribeGroupPlan
+      loadGroupCreditCard, updateGroupCreditCard } = actions
+
     { resetValues } = CardFormValues.actions

     options =
@@ -47,10 +51,16 @@
       cardName: formValues.get 'fullName'

     createStripeToken(options).then ({ token }) ->
-      subscribeGroupPlan({ token, email: formValues.get 'email' }).then ->
-        resetValues()
-        loadGroupCreditCard()

+      if getters.paymentValues().get 'groupCreditCard'
+        updateGroupCreditCard({ token }).then ->
+          resetValues()
+          loadGroupCreditCard()
+
+      else
+        subscribeGroupPlan({ token, email: formValues.get 'email' }).then ->
+          resetValues()
+          loadGroupCreditCard()

   render: ->


Fixed Angular.js pre-processing

Fixed Angular.js pre-processing

--- gulp/client-scripts.coffee
+++ gulp/client-scripts.coffee
@@ -51,7 +51,7 @@
     .pipe gulpif config.concat, concat 'index.js'

     # Angular.js annotations pre-processing...
-    .pipe Annotate add: yes, remove: yes, single_quotes: yes
+    .pipe ngAnnotate add: yes, remove: yes, single_quotes: yes

     # Parse js files and make them "nicer" (Uglify)
     .pipe gulpif config.minify, uglify()


Reply to private dictionary queries via private messages

Previously an entry would have been sent into the same room as the user requesting it is in.

--- handlers/dictionary.coffee
+++ handlers/dictionary.coffee
@@ -64,7 +64,12 @@
 			if err?
 				debug "Error while checking key “#{key}”: #{err}"
 			else if row?
-				api.sendMessage "[#{key}] #{row.value}", no, message.roomID, callback
+				reply = "[#{key}] #{row.value}"
+
+				if message.type is common.messageTypes.WHISPER
+					api.replyTo message, reply, no, callback
+				else
+					api.sendMessage reply, no, message.roomID, callback
 			else
 				callback?()
 	else if message.message[0] is '.' # check other dictionary commands


Select discussions feed by default in KL mode

--- core/app/backbone/views/react_feed_selection.coffee
+++ core/app/backbone/views/react_feed_selection.coffee
@@ -3,7 +3,7 @@
   mixins: [UpdateOnSignInOrOutMixin]

   getInitialState: ->
-    feedChoice: 'global'
+    feedChoice: if window.is_kennisland then 'discussions' else 'global'
     feeds:
       global: new GlobalFeedActivities
       personal: new PersonalFeedActivities


Fix an error where changing 'live' to 'on' broke account summary lines from collapsing Even though jQuery has deprecated the use of 'live' for 'on', using 'on' results in a summary line not automatically collapsing when another summary line is expanded. We revert back to 'live' and also recode using an event map for both the expand and collapse events.

--- src/account/summary.coffee
+++ src/account/summary.coffee
@@ -133,20 +133,21 @@
 		# Upon a plugin refresh, we will refresh all summary lines.
 		.refresh refresh_all

-		$('.account_summary', @)
-
-		# Upon the user expanding a summary line,
-		# we will refresh the line and its inner plugins.
-		.on 'expand', (e, ui) ->
-			$(@).publish $('h3', @).prop 'id' # The h3 element id is the name of the data channel to publish on
-			$('.plugin', @).refresh()
-			return false
-
-		# Upon the user collapsing a summary line,
-		# we will empty its inner plugin content.
-		.on 'collapse', (e, ui) ->
-			$('.plugin', @).empty()
-			return false
+		# Upon the user expanding/collapsing a summary line,
+		# we will refresh/empty its inner plugins.
+		# FIXME: even though 'live' is deprecated in favour of 'on', using 'on'
+		# doesn't collapse.
+		$('.account_summary', @).live
+			expand: ->
+				$('.plugin', @).refresh()
+				# When expanding, the summary line itself should be refreshed,
+				# by having the inner plugin publish on a topic name that is
+				# determined by the h3 element id.
+				.publish "account.#{$('h3', @).prop 'id'}"
+				return false
+			collapse: ->
+				$('.plugin', @).empty()
+				return false

 		# Upon the user logging in,
 		# we will refresh a summary line's inner plugin content if the summary line is not collapsed.


Remove unneeded options.

Remove unneeded options.

--- bin/main.coffee
+++ bin/main.coffee
@@ -29,10 +29,6 @@
   [ '-j', "--jenkins-job job/type", 'A job in the format "job[/type]". This can be list of comma separated list. "type" has the default "lastBuild" and can have the following jenkins types: lastBuild, lastStableBuild, lastSuccessfulBuild, lstFailedBuild, lastUnsuccessfulBuild' ]
   [ '-t', "--text-file filename", "Specify a path to an alternative JSON file with texts." ]
   [ '-p', "--plugins name", "Specify a plugin by its name. This can be a list of comma separated list." ]
-  # this option will be dismissed later in favor of an plugin "http server"
-  [ '-nabaztagpln', '--nabaztag-public-localhost-name VALUE', 'Define the hostname (or addr) of this machine to allow access to the audio files. Default is "localhost".']
-  # this option will be dismissed later in favor of an plugin "http server"
-  [ '-nabaztagplp', '--nabaztag-public-localhost-port VALUE', 'Define the port of this machine to allow access to the audio files. Default is "80".']
 ]

 Options =


WIP: ExpressionEvaluators refactored

WIP: ExpressionEvaluators refactored

--- src/main/code/ExpressionEvaluators.coffee
+++ src/main/code/ExpressionEvaluators.coffee
@@ -1,41 +1,54 @@
 NotCalculated = 'NOT_CALCULATED'

-class Literal
-  constructor: (@expr, @value) ->
-    @values = [@value]
-    @latest = @value
-
-  newValues: ->
-#    console.log 'newValues', this
-    @values
-
-  latestValue: -> @latest
-
-  reset: -> @values = []
-
-class BinaryOperator
-  constructor: (@expr, @left, @right) ->
+class Evaluator
+  constructor: (@expr) ->
     @values = NotCalculated
     @latest = NotCalculated

-  op: (a, b) -> throw new Error('op must be defined')
-
   newValues: ->
     if @values is NotCalculated
-      leftValues = @left.newValues()
-      rightValues = @right.newValues()
-      if leftValues.length or rightValues.length
-        @values = [@op(@left.latestValue(), @right.latestValue())]
-        @latest = NotCalculated
+      @values = @getNewValues()
+      if @values.length
+        @latest = @values[0]
+
+    #    console.log 'newValues', this
     @values

   latestValue: ->
     @newValues() #ensure use any new values
     if @latest is NotCalculated
-      @latest = @op @left.latestValue(), @right.latestValue()
+      @latest = @getLatestValue()
+
+    #    console.log 'latestValue', this
     @latest

+  getNewValues: -> throw new Error('getNewValues must be defined')
+  getLatestValue: -> throw new Error('getLatestValue must be defined')
+
   reset: -> @values = NotCalculated
+
+class Literal extends Evaluator
+  constructor: (expr, value) ->
+    super expr
+    @values = [value]
+    @latest = value
+
+  reset: -> @values = []
+
+
+class BinaryOperator extends Evaluator
+  constructor: (expr, @left, @right) -> super expr
+
+  op: (a, b) -> throw new Error('op must be defined')
+
+  getNewValues: ->
+    leftValues = @left.newValues()
+    rightValues = @right.newValues()
+    if leftValues.length or rightValues.length
+      [@op(@left.latestValue(), @right.latestValue())]
+    else []
+
+  getLatestValue: -> @op @left.latestValue(), @right.latestValue()

 class Add extends BinaryOperator
   op: (a, b) -> a + b
@@ -43,28 +56,9 @@
 class Subtract extends BinaryOperator
   op: (a, b) -> a - b

-class FunctionCallNoArgs
-  constructor: (@expr, @name, @sheet) ->
-    @values = NotCalculated
-    @latest = NotCalculated
-
-  newValues: ->
-    if @values is NotCalculated
-      @values = @sheet[@name].newValues()
-      if @values.length
-        @latest = NotCalculated
-
-    #    console.log 'newValues', this
-    @values
-
-  latestValue: ->
-    @newValues() #ensure use any new values
-    if @latest is NotCalculated
-      @latest = @sheet[@name].latestValue()
-
-    #    console.log 'latestValue', this
-    @latest
-
-  reset: -> @values = NotCalculated
+class FunctionCallNoArgs extends Evaluator
+  constructor: (@expr, @name, @sheet) -> super expr
+  getNewValues: -> @sheet[@name].newValues()
+  getLatestValue: -> @sheet[@name].latestValue()

 module.exports = {Literal, Add, Subtract, FunctionCallNoArgs}


webpack assumes '-loader' suffix

webpack assumes '-loader' suffix

Most guides on the interwebs also ommit it

--- configs/webpack/base.coffee
+++ configs/webpack/base.coffee
@@ -42,13 +42,13 @@
       /\/sinon\.js/
     ]
     loaders: [
-      { test: /\.coffee$/, loader: "coffee-loader"     }
-      { test: /\.json$/,   loader: "json-loader"       }
-      { test: /\.cjsx$/,   loader: "coffee-jsx-loader" }
+      { test: /\.coffee$/, loader: "coffee"     }
+      { test: /\.json$/,   loader: "json"       }
+      { test: /\.cjsx$/,   loader: "coffee-jsx" }
     ]
     preLoaders: [{
       test: /\.(cjsx|coffee)$/
-      loader: "coffeelint-loader"
+      loader: "coffeelint"
       exclude: /(node_modules|resources|bower_components)/
     }]

@@ -57,8 +57,8 @@
     # less, coffee, and cjsx will have ["react-hot"]
     # added in dev config.
     { test: /\.less$/,   loader: ExtractTextPlugin.extract('css!less') }
-    { test: /\.coffee$/, loader: 'coffee-loader' }
-    { test: /\.cjsx$/,   loader: 'coffee-jsx-loader' }
+    { test: /\.coffee$/, loader: 'coffee' }
+    { test: /\.cjsx$/,   loader: 'coffee-jsx' }
   ]

 DEV_LOADERS = ['react-hot'] #, 'webpack-module-hot-accept']
@@ -66,8 +66,8 @@
 BASE_DEV_LOADERS =
   [
     { test: /\.coffee$/, loaders: DEV_LOADERS.concat('coffee')}
-    { test: /\.cjsx$/,   loaders: DEV_LOADERS.concat('coffee-jsx-loader')}
-    { test: /\.less$/,   loaders: DEV_LOADERS.concat('style-loader', 'css-loader', 'less-loader') }
+    { test: /\.cjsx$/,   loaders: DEV_LOADERS.concat('coffee-jsx')}
+    { test: /\.less$/,   loaders: DEV_LOADERS.concat('style', 'css', 'less') }
   ]

 mergeWebpackConfigs = ->


fix(cache/get): in tests stub `fs.existsSync` individually in test cases

--- test/cache/get.spec.coffee
+++ test/cache/get.spec.coffee
@@ -11,9 +11,9 @@
 path = require "path"

 describe "cache.get", ->
-	fakeExistsSync = sinon.stub()
+	it "returns null when package name isn't specified", ->
+		fakeExistsSync = sinon.stub()

-	it "returns null when package name isn't specified", ->
 		get = proxyquire "../../lib/cache/get",
 			"../util":
 				getPathToMcpmDir: -> "fake-.mcpm"
@@ -28,6 +28,8 @@
 	for name in [ "", "-", "1sdf", "π", "mcpm/mcpm" ]
 		do ( name ) ->
 			it "returns null when package name isn't valid: " + name, ->
+				fakeExistsSync = sinon.stub()
+
 				get = proxyquire "../../lib/cache/get",
 					"../util":
 						getPathToMcpmDir: -> "fake-.mcpm"


Added suffix to record header, header 80 wide.

 * Added left alinged data to the record header that displayes the process tag
   and pid of the message.
 * Adjusted headers to be exactly 80 characters wide.

--- bin/tail.coffee
+++ bin/tail.coffee
@@ -154,9 +154,9 @@
     length = (dashes.length - suffix.length - 1)
     length-- if length % 2
     dashes = dashes.substring 0, length
-    "#{prefix} #{dashes} #{suffix}"
-  else
-    "#{prefix} #{dashes}"
+    "#{prefix} -#{dashes} #{suffix}"
+  else
+    "#{prefix} -#{dashes}"

 writeExceptions = (exceptions, message) ->
   exception = exceptions.shift()
@@ -214,9 +214,10 @@
           for record in output
             process.stdout.write separator
             separator = "\n"
-            header = "#{tz("%Y/%m/%d %H:%M:%S", record.date)} on #{record.host} "
-            header += new Array(80 - header.length).join("-")
-            process.stdout.write "#{header}\n\n"
+            prefix = "#{tz("%Y/%m/%d %H:%M:%S", record.date)} on #{record.host}"
+            suffix = "#{record.program}[#{record.pid}]"
+            dashes = new Array(80 - (prefix.length + suffix.length + 1)).join("-")
+            process.stdout.write "#{prefix} #{dashes} #{suffix}\n\n"
             process.stdout.write "#{record.message}\n"
             if record.json
               if record.exceptions


fixed bug where supplying an array of values with improperly formatted header fields crashed the server

--- src/models/requestresponse.coffee
+++ src/models/requestresponse.coffee
@@ -40,7 +40,7 @@
                   method : row.method
                   post : row.post
                response :
-                  headers : JSON.parse row.headers
+                  headers : if typeof data.headers is "object" then JSON.parse data.headers else ""
                   content : row.content
                   status : row.status
          return toReturn
@@ -52,7 +52,7 @@
                method : data.method
                post : data.post
             response :
-               headers : JSON.parse data.headers
+               headers : if typeof data.headers is "object" then JSON.parse data.headers else ""
                content : data.content
                status : data.status


Add some comments

Add some comments

--- server.coffee
+++ server.coffee
@@ -27,12 +27,14 @@

   coll = db.collection 'rtc'

-  # stat helper
+  # abstraction for mapReduce page calls

   add_map_reduce_stat = (name, map, reduce) =>
     path = '/stats/' + name + '.json'

     page_fun = (req, res) =>
+      # fetch various options from the request
+
       get_int = (key, fallback) =>
         if req.param(key)?
           return parseInt(req.param(key))
@@ -61,16 +63,24 @@
       scope.min_peak = get_int('min_peak', 0)
       scope.max_peak = get_int('max_peak', Number.MAX_VALUE)

+      # function to assign times to a time frame
+
       scope.adjust_time = (time) =>
         if scale
+          # very simple time frame based on modulo
           return time - time % scale
         else if steps?
+          # user defined steps (plus start as implicit step)
           last = start
           for step in steps
             if step <= time
               return step
+          return last
         else
+          # no steps, return every single event
           return time
+
+      # prepare options for mapReduce

       options =
         out:
@@ -81,17 +91,26 @@
             $gte: start,
             $lt: end,

+      # finally call mapReduce ...
+
       coll.mapReduce map, reduce, options, (err, result) =>
+        # this should not happen
         if err
           res.send({error: err})
           return
+
+        # transform array into object

         data = {}

         for item in result
           data[item._id] = item.value

+        # finished, send to user
+
         res.send data
+
+    # callable as post and get

     app.get path, page_fun
     app.post path, page_fun


BitBucket fixes

BitBucket fixes

- Fix ordering of commits, newest should have the highest id
- Use commit specific author instead of repository author
- Ignore commits that are not on the master branch (should be an admin setting probably, or save the branch and allow filtering it)

--- server.coffee
+++ server.coffee
@@ -12,10 +12,6 @@

 	if parsed.push?.changes?
 		general = {}
-		general.byName = parsed.actor?.display_name
-		general.byUsername = parsed.actor?.username
-		general.byUUID = parsed.actor?.uuid
-		general.avatar = parsed.actor?.links?.avatar?.href
 		general.repositoryName = parsed.repository?.name
 		general.repositoryFullName = parsed.repository?.full_name
 		general.received = new Date()/1000
@@ -26,8 +22,19 @@
 				log 'no commits in change'
 				continue

-			for commit in change.commits
+			if change.new?.type is "branch" and change.new?.name isnt "master"
+				log 'not on master branch'
+				return
+
+			# If the push had more than 5 commits truncated will be true and only the last 5 commits are in here
+			# First commit is newest and we want the oldest commit to have the lowest id, so reverse
+			for commit in change.commits.reverse()
 				commitData = {}
+				user = commit.author?.user
+				commitData.byName = user?.display_name
+				commitData.byUsername = user?.username
+				commitData.byUUID = user?.uuid
+				commitData.avatar = user?.links?.avatar?.href
 				commitData.message = commit.message
 				commitData.date = commit.date
 				commitData.hash = commit.hash


Set private key from meshblu device

Set private key from meshblu device

--- server.coffee
+++ server.coffee
@@ -54,6 +54,10 @@

 meshbluConn.on 'ready', =>
   debug 'Connected to meshblu'
+
+  meshbluConn.whoami {}, (device) ->
+    conn.setPrivateKey(device.privateKey) unless conn.privateKey
+
   app.listen port, =>
     debug "Meshblu Google Authenticator..."
     debug "Listening at localhost:#{port}"


remove useless lines

remove useless lines

--- server.coffee
+++ server.coffee
@@ -14,7 +14,5 @@

 americano.start params, (app, server) ->
     realtime = RealtimeAdapter server: server, ['konnector.update']
-
     initKonnectors ->
         patchKonnectors -> poller.start()
-


allow for disabling commenting on pros/cons

implemented with  "disable_comments" true customization

--- @client/point.coffee
+++ @client/point.coffee
@@ -139,7 +139,7 @@
     LI
       key: "point-#{point.id}"
       'data-id': @props.key
-      className: "point #{@props.rendered_as} #{if point.is_pro then 'pro' else 'con'}"
+      className: "point #{@props.rendered_as} #{if point.is_pro then 'pro' else 'con'} #{if customization('disable_comments') && !expand_to_see_details then 'commenting-disabled' else ''}"
       onClick: @selectPoint
       onTouchEnd: @selectPoint
       onKeyDown: (e) =>
@@ -246,20 +246,21 @@
               if !screencasting() && !embedded_demo() && fetch('/subdomain').name != 'galacticfederation'
                 [
                   prettyDate(point.created_at)
-                  ', '
+                  SPAN style: paddingLeft: 8
                 ]

-              SPAN
-                key: 2
-                style: {whiteSpace: 'nowrap'}
-
-                A
-                  className: 'select_point'
-
-                  translator
-                    id: 'engage.link_to_comments'
-                    comment_count: point.comment_count
-                    "{comment_count, plural, one {# comment} other {# comments}}"
+              if !customization('disable_comments')
+                SPAN
+                  key: 2
+                  style: {whiteSpace: 'nowrap'}
+
+                  A
+                    className: 'select_point'
+
+                    translator
+                      id: 'engage.link_to_comments'
+                      comment_count: point.comment_count
+                      "{comment_count, plural, one {# comment} other {# comments}}"

@@ -518,6 +519,10 @@

   selectPoint: (e) ->
     e.stopPropagation()
+    point = @data()
+
+    return if !point.text && customization('disable_comments')
+

     # android browser needs to respond to this via a touch event;
     # all other browsers via click event. iOS fails to select
@@ -598,6 +603,14 @@
 /* war! disabled jquery UI draggable class defined with !important */
 .point_content.ui-draggable-disabled {
   cursor: pointer !important; }
+
+.commenting-disabled .point_content.ui-draggable-disabled {
+  cursor: auto !important; }
+
+
+.commenting-disabled .point_details_tease {
+  cursor: auto;
+}

 #{css.grab_cursor('.point_content.ui-draggable')}

@@ -870,25 +883,25 @@
             splitParagraphs(point.text)

-
-      SECTION null,
-        HEADING
-          style:
-            textAlign: 'left'
-            fontSize: 24
-            color: focus_color()
-            marginBottom: 25
-            marginTop: 10
-            fontWeight: 600
-
-          translator "engage.point_details_heading", 'Discuss this Point'
-
-        DIV className: 'comments',
-          for comment in comments
-            Comment key: comment.key
-
-        # Write a new comment
-        EditComment fresh: true, point: arest.key_id(@props.key)
+      if !customization('disable_comments')
+        SECTION null,
+          HEADING
+            style:
+              textAlign: 'left'
+              fontSize: 24
+              color: focus_color()
+              marginBottom: 25
+              marginTop: 10
+              fontWeight: 600
+
+            translator "engage.point_details_heading", 'Discuss this Point'
+
+          DIV className: 'comments',
+            for comment in comments
+              Comment key: comment.key
+
+          # Write a new comment
+          EditComment fresh: true, point: arest.key_id(@props.key)

   # HACK! Save the height of the open point, which will be added
   # to the min height of the reasons region to accommodate the


engine data module

engine data module

--- test/engine_data.coffee
+++ test/engine_data.coffee
@@ -7,6 +7,6 @@
   engine.data.score = 0
   engine.data.score += 50

-  equals
+  equals engine.data.score,

 module()


Use index based comparisons to prevent first option selection

--- src/coffee/cilantro/ui/controls/select.coffee
+++ src/coffee/cilantro/ui/controls/select.coffee
@@ -68,8 +68,11 @@
             @render()

         onSelectionChange: (event) ->
+            option = @ui.items.children[0]
+
             @collection.each (model) ->
-                model.set('selected', model.get('value') == event.target.value)
+                if event.target isnt option
+                    model.set('selected', model.get('value') == event.target.value)

             @change()

@@ -86,7 +89,7 @@
             selection = @collection.findWhere(selected: true)

             # We don't want to include the placeholder as a valid value
-            if selection? and selection.get('value') != '---'
+            if selection? and @collection.indexOf(selection) > 0
                 return selection.get('value')

             return null


change linda-server default port:8931  #2

--- bin/linda-server.coffee
+++ bin/linda-server.coffee
@@ -12,7 +12,7 @@
 ]

 config =
-  port: 3000
+  port: process.env.PORT or 8931

 parser.on 'help', ->
   package_json = require "#{__dirname}/../package.json"
@@ -21,7 +21,7 @@

   Usage:
     % linda-server
-    % linda-server --port 3000
+    % linda-server --port 8931
     % DEBUG=* linda-server
   """
   console.log parser.toString()


add boolean for quick use setting toggling

--- tutor/src/components/course-calendar/add-assignment-sidebar.cjsx
+++ tutor/src/components/course-calendar/add-assignment-sidebar.cjsx
@@ -3,6 +3,7 @@
 isEmpty = require 'lodash/isEmpty'

 classnames = require 'classnames'
+{UiSettings} = require 'shared'

 {AddAssignmentLink} = require './task-dnd'

@@ -13,6 +14,9 @@

 {CourseStore} = require '../../flux/course'
 {PastTaskPlansStore} = require '../../flux/past-task-plans'
+
+IS_INTRO_VIEWED = 'viewed-plan-dnd-intro'
+USE_SETTINGS = false

 AddAssignmentSidebar = React.createClass

@@ -32,7 +36,12 @@
     @setState(needsIntro: PastTaskPlansStore.hasPlans(@props.courseId))

   shouldShowIntro: ->
-    @props.isOpen and @state.needsIntro and @state.showIntro
+    shouldIntro = if USE_SETTINGS then not UiSettings.get(IS_INTRO_VIEWED) else true
+
+    @props.isOpen and
+      @state.needsIntro and
+      shouldIntro and
+      @state.showIntro

   renderMenuLink: (link) ->
     <AddAssignmentLink
@@ -43,6 +52,7 @@
     />

   closeHelp: ->
+    UiSettings.set(IS_INTRO_VIEWED, true) if USE_SETTINGS
     @setState(showIntro: false)

   Intro: ->


stricter tests

stricter tests

--- test/integration.coffee
+++ test/integration.coffee
@@ -139,16 +139,16 @@
     c =
       factories:
         a: (b, c) ->
-          test.ok callsTo.a < 2
+          test.equal callsTo.a, 1
           Promise.delay(b + c, 40)
         b: (d) ->
-          test.ok callsTo.b < 2
+          test.equal callsTo.b, 1
           Promise.delay(d + 1, 20)
         c: (d) ->
-          test.ok callsTo.c < 2
+          test.equal callsTo.c, 1
           Promise.delay(d + 2, 30)
         d: ->
-          test.ok callsTo.d < 2
+          test.equal callsTo.d, 1
           Promise.delay(10, 10)

     hinoki.get(c, 'a').then (a) ->


Adjust animation speed

Adjust animation speed

--- resources/assets/coffee/_classes/nav2.coffee
+++ resources/assets/coffee/_classes/nav2.coffee
@@ -57,11 +57,11 @@
   autoMobileNav: (e, {target, tree}) =>
     if target == 'mobile-menu'
       @clickMenu.show('mobile-nav')
-      Timeout.set 0, => $(@clickMenu.menu('mobile-menu')).finish().slideDown()
+      Timeout.set 0, => $(@clickMenu.menu('mobile-menu')).finish().slideDown(150)

     if tree.indexOf('mobile-menu') == -1
       Blackout.hide()
-      Timeout.set 0, => $(@clickMenu.menu('mobile-menu')).finish().slideUp()
+      Timeout.set 0, => $(@clickMenu.menu('mobile-menu')).finish().slideUp(150)
     else
       Blackout.show()


removed some dead code

removed some dead code

--- client/editor.coffee
+++ client/editor.coffee
@@ -206,18 +206,6 @@
   saved: (err) -> (if err then $.alert 'Cannot save changes' else @emit 'CloseWindow', win: @id); return
   closePopup: -> (if D.floating then window.onbeforeunload = null; D.forceClose = 1; close()); return
   die: -> @cm.setOption 'readOnly', true; return
-  getOpts: -> @opts
-  getState: ->
-    {
-      hll: @hll, originalText: @originalText, originalBreakpoints: @originalBreakpoints, breakpoints: @breakpoints[..]
-      value: @cm.getValue(), cursorIndex: @cm.indexFromPos(@cm.getCursor()), mode: @cm.getOption('mode')
-    }
-  setState: (h) ->
-    @cm.setValue h.value; @cm.clearHistory()
-    @cm.setCursor @cm.posFromIndex h.cursorIndex; {@originalText, @originalBreakpoints, @breakpoints} = h
-    @cm.setOption 'mode', h.mode
-    for l in @breakpoints then @cm.setGutterMarker l, 'breakpoints', @createBreakpointElement()
-    @highlight h.hll; return
   getDocument: -> @$e[0].ownerDocument
   refresh: -> @cm.refresh(); return
   cword: -> # APL identifier under cursor


Commented out debugging code

Commented out debugging code

--- views/navigation-bar.cjsx
+++ views/navigation-bar.cjsx
@@ -26,10 +26,10 @@
   webview.addEventListener ev, psLog.bind(@, ev)

 if remote.getCurrentWindow().isDevToolsOpened()
-  psAddEventLogger 'did-start-loading'
-  psAddEventLogger 'did-stop-loading'
+  # psAddEventLogger 'did-start-loading'
+  # psAddEventLogger 'did-stop-loading'
   # psAddEventLogger 'did-finish-load'
-  psAddEventLogger 'did-fail-load'
+  # psAddEventLogger 'did-fail-load'

 NavigationBar = React.createClass
   canSetState: true


Make variadic options more explicit

Make variadic options more explicit

--- services/real-time/app/coffee/Router.coffee
+++ services/real-time/app/coffee/Router.coffee
@@ -85,17 +85,22 @@
 			# doc_id, options, callback
 			# doc_id, fromVersion, options, callback
 			client.on "joinDoc", (doc_id, fromVersion, options, callback) ->
-				if typeof fromVersion == "function"
+				if typeof fromVersion == "function" and !options
+					callback = fromVersion
 					fromVersion = -1
 					options = {}
-					callback = fromVersion
-				else if typeof fromVersion == "object"
+				else if typeof fromVersion == "number" and typeof options == "function"
+					callback = options
+					options = {}
+				else if typeof fromVersion == "object" and typeof options == "function"
+					callback = options
+					options = fromVersion
 					fromVersion = -1
-					options = fromVersion
-					callback = options
-				if typeof options == "function"
-					options = {}
-					callback = options
+				else if typeof fromVersion == "number" and typeof options == "object"
+					# Called with 4 args, things are as expected
+				else
+					logger.error { arguments: arguments }, "unexpected arguments"
+					return {} # ????

 				WebsocketController.joinDoc client, doc_id, options, fromVersion, (err, args...) ->
 					if err?


unused file removed

unused file removed

--- app-utils.coffee
+++ app-utils.coffee
@@ -1,105 +0,0 @@
-@include = ->
-
-  _ = require "underscore"
-
-
-  ##
-  # Creates a pivot table out of a list of objects
-  # returns [table, columns]
-  #          table - list of objects representing rows of the table
-  #          columns - full list of the columns (empty values are omitted in the row objects)
-  #
-  # columnProp - for each distinct value of this prop a column will be
-  #              created in the pivot table
-  # cellValueProp - property to be used for the cell values of the pivot
-  # rowIdProps - properties which identify a row
-  #
-  # All the other properties of the input objects are ignored.
-  ##
-  pivotTable : (objList, columnProp, cellValueProp, rowIdProps) ->
-    ##
-    # nestedProp(obj, [a,b,c]) will return obj[a][b][c]
-    ##
-    nestedProp = (obj, props, createIfMissing) ->
-      i = 0
-      value = obj
-      while value?  and  i < props.length
-        p = props[i]
-        if createIfMissing
-          unless value[p]? then value[p] = {}
-        value = value[p]
-        i++
-      value
-
-
-    flattenNestedObj = (obj, propNames) ->
-      copy = (obj) ->
-        newobj = {}
-        for key of obj
-          newobj[key] = obj[key]
-        newobj
-
-      iterate = (obj, row, level) ->
-        #console.log "iterate "+ level + " "+propNames[level]
-        if level < propNames.length
-          for key, value of obj
-            o = copy(row)
-            o[propNames[level]] = key
-            iterate(value, o, level + 1)
-        else
-          for key, value of obj
-            row[key] = value
-          table.push row
-
-      table = []
-      #console.log obj
-      iterate(obj, {}, 0)
-      #console.log _.first(table, 5)
-      table
-
-    rows = {}
-    rowFor = (obj) ->
-      id = rowIdProps.map (p) -> obj[p]
-      nestedProp(rows, id, true)
-
-    newColumns = {}
-    for obj in objList
-      col = obj[columnProp]
-      if col?
-        newColumns[col] = true
-        row = rowFor(obj)
-        row[col] = obj[cellValueProp]
-
-    #console.log rows
-
-    columns = _.union(rowIdProps, _.keys(newColumns).sort())
-
-    [flattenNestedObj(rows, rowIdProps), columns]
-
-
-
-
-
-  ##
-  # Generate CSV out of a list of objects (e.g. list of results of a SELECT query).
-  #
-  # If columns are not specified, the property names of the first element are used.
-  ##
-  objListToCsv : (objList, columns) ->
-    unless columns
-      columns = (k for k of objList[0])
-
-    list2csv = (list) ->
-      list.map((v) ->
-        if (not(v?) or v.length == 0) then ""
-        else if (v.indexOf? and v.indexOf(",") >= 0) then '"'+v.replace('"','\"')+'"'
-        else v
-      ).join(",")
-
-    obj2csv = (obj) -> list2csv(columns.map((k) -> obj[k]))
-
-    list2csv(columns) + "\n" +
-    objList.map(obj2csv).join("\n")
-
-
-


Payment: showloader while waiting for recurly response

--- client/Main/payment/paymentworkflow.coffee
+++ client/Main/payment/paymentworkflow.coffee
@@ -1,6 +1,8 @@
 class PaymentWorkflow extends FormWorkflow

   constructor: (options = {}, data) ->
+
+    options.cssClass = KD.utils.curry 'payment-workflow', options.cssClass
     unless options.confirmForm
       throw new Error "You must provide a confirmForm option!"

@@ -10,7 +12,10 @@

     paymentController = KD.getSingleton 'paymentController'

+    @showLoader()
+
     paymentController.fetchPaymentMethods (err, paymentMethods) =>
+      @hideLoader()
       return if KD.showError err

       if paymentMethods.methods.length > 0


refactor

refactor

--- src/script/portal-minicart.coffee
+++ src/script/portal-minicart.coffee
@@ -144,18 +144,19 @@
 				item.formattedPrice = _.intAsCurrency(item.sellingPrice, @options) + if item.measurementUnit and item.measurementUnit != 'un' then " (por cada #{item.unitMultiplier} #{item.measurementUnit})" else ''

 		# Resolve first delivery window
-		slas = @cartData.shippingData.logisticsInfo[0].slas
-		for sla, i in slas
+		@cartData.slas = @cartData.shippingData.logisticsInfo[0].slas
+		for sla, i in @cartData.slas
 			for deliveryId in sla.deliveryIds
 				if deliveryId.courierName is 'Entrega Agendada'
 					scheduledDeliverySlaId = i
 					break
-		@cartData.firstAvailableDeliveryWindow = slas[scheduledDeliverySlaId].availableDeliveryWindows[0]
+		@cartData.deliveryWindows = @cartData.slas[scheduledDeliverySlaId].availableDeliveryWindows
+		@cartData.firstAvailableDeliveryWindow = @cartData.deliveryWindows[0]

 		# Resolve available timetables on the same day of first available delivery window
 		firstAvailableDay = new Date(@cartData.firstAvailableDeliveryWindow.startDateUtc).getDate()
 		@cartData.firstAvailableDeliveryWindow.timetable = []
-		for deliveryWindow in slas[scheduledDeliverySlaId].availableDeliveryWindows
+		for deliveryWindow in @cartData.deliveryWindows
 			date = new Date(deliveryWindow.startDateUtc)
 			if date.getDate() is firstAvailableDay
 				@cartData.firstAvailableDeliveryWindow.timetable.push(deliveryWindow)


implement setting functions

implement setting functions

--- public/coffeescript/user/UserController.coffee
+++ public/coffeescript/user/UserController.coffee
@@ -1,13 +1,41 @@
+
+'use strict'

 Dillinger = require('../dillinger')

-BaseCtrl = Dillinger.controller 'UserController',
-  ($scope, UserService) ->
-
-    'use strict'
+module.exports = Dillinger.controller 'UserController',
+  ($scope, $rootScope, $timeout, UserService, DocService, Wordcounter) ->

     $scope.user = UserService.user

+    toggleAutoSave = ->
+      $scope.user.AutoSave = !$scope.user.AutoSave
+      UserService.save()
+
+    toggleWordCount = ->
+      $scope.user.WordCount = !$scope.user.WordCount
+      UserService.save()
+
+    toggleNightMode = ->
+      $scope.user.NightMode = !$scope.user.NightMode
+      UserService.save()
+
+    resetProfile = ->
+      sessionStorage.clear()
+      window.location.reload()
+      false
+
+    updateWords = ->
+      $rootScope.words = Wordcounter.count()
+      $timeout ->
+        $scope.$apply()
+      , 0
+
+    $scope.toggleAutoSave = toggleAutoSave
+    $scope.toggleWordCount = toggleWordCount
+    $scope.toggleNightMode = toggleNightMode
+    $scope.resetProfile = resetProfile
+
+    $rootScope.$on 'preview.updated', updateWords
+
     return
-
-module.exports = BaseCtrl


JMachine: require JUser when needed to avoid circular dependencies

--- workers/social/lib/social/models/computeproviders/machine.coffee
+++ workers/social/lib/social/models/computeproviders/machine.coffee
@@ -5,8 +5,6 @@

 {argv}      = require 'optimist'
 KONFIG      = require('koding-config-manager').load("main.#{argv.c}")
-
-JUser       = require '../user'

 module.exports = class JMachine extends Module

@@ -384,6 +382,7 @@
     err   = -> callback new KodingError 'Owner user not found'
     return err()  unless owner

+    JUser = require '../user'
     JUser.one _id: owner.id, (err, user) ->
       return err()  if err or not user
       user.fetchOwnAccount callback


余計なものが残ってたので削除

余計なものが残ってたので削除

--- public/javascripts/src/bell-team-selector.cjsx
+++ public/javascripts/src/bell-team-selector.cjsx
@@ -19,12 +19,6 @@
             that.setState
                 teams : data
     render: ->
-        options = @state.teams.map (team) ->
-            <option value={team} key={team}>{team}</option>
-        <div>
-        <select multiple={true}>
-            {options}
-        </select>
         <SelectField
             value={@state.selectValue}
             onChange={@onChange.bind(null, 'selectValue')}


Removed unnecessary require

Removed unnecessary require

--- src/coffee/renderer.coffee
+++ src/coffee/renderer.coffee
@@ -1,4 +1,3 @@
-mandelbrot = require './mandelbrot'
 region = require './region'

 canvas = document.getElementById 'canvas'


links

links

--- editor.coffee
+++ editor.coffee
@@ -3,6 +3,8 @@
 default_options = {}

 default_target_command = 'img'
+
+previously_run = null

 graphite_function_docs = {}
 $.getJSON 'functions.fjson', (data) ->
@@ -418,6 +420,15 @@
               context.success()
           _lead_finished

+      permalink:
+        cmd 'Create a link to the previously run statement', ->
+          a = document.createElement 'a'
+          a.href = location.href
+          a.search = '?' + encodeURIComponent btoa previously_run if previously_run?
+          a.innerText = a.href
+          context.$result.append a
+          context.success()
+
       q: do ->
         result = (targets...) ->
           for t in targets
@@ -507,9 +518,13 @@
                 ns.example "#{f} #{result.to_js_string()}"
           else
             ns.object result
+        previously_run = string
       catch e
         handle_exception e, compiled

     $entry.append $result

-  run 'intro'
+  if location.search isnt ''
+    run atob decodeURIComponent location.search[1..]
+  else
+    run 'intro'


Bugfix in loaders

Bugfix in loaders

--- src/goo/loaders/handlers/ShaderHandler.coffee
+++ src/goo/loaders/handlers/ShaderHandler.coffee
@@ -55,7 +55,7 @@
 				if config.defines
 					shaderDefinition.defines = config.defines
 			else
-				shaderDefinition = this._getDefaultShaderDefinition()
+				shaderDefinition = @_getDefaultShaderDefinition()

 			promises = [
 				@getConfig(config.vshaderRef)
@@ -86,3 +86,21 @@

 		remove: (ref)->
 			# delete @_objects[ref]
+
+		_getDefaultShaderDefinition: ->
+			attributes :
+				vertexPosition : MeshData.POSITION
+				vertexNormal : MeshData.NORMAL
+				vertexUV0 : MeshData.TEXCOORD0
+			uniforms :
+				viewMatrix : Shader.VIEW_MATRIX
+				projectionMatrix : Shader.PROJECTION_MATRIX
+				worldMatrix : Shader.WORLD_MATRIX
+				cameraPosition : Shader.CAMERA
+				lightPosition : Shader.LIGHT0
+				diffuseMap : Shader.DIFFUSE_MAP
+				materialAmbient : Shader.AMBIENT
+				materialDiffuse : Shader.DIFFUSE
+				materialSpecular : Shader.SPECULAR
+				materialSpecularPower : Shader.SPECULAR_POWER
+


Fix the way usernames are replaced.

Fix the way usernames are replaced.

--- prviewer.coffee
+++ prviewer.coffee
@@ -306,23 +306,23 @@
       # Add reviewers list to pull object.
       pull.reviewers = (k for k, v of reviewers)

-      # Replace user objects with username/avatar
-      makeUserObj = (username) ->
+      # Replace usernames with { username, avatar } objects for the template.
+      getUserObj = (username) ->
         return getFrom(token, username).then (user) ->
           obj = { username: username, avatar: user.avatar_url }
           return Q(obj)

-      userPromises = [
-        makeUserObj(pull.user.login).then (obj) -> pull.submitter = obj
-      ]
+      promises = []
+
+      promises.push getUserObj(pull.user.login).then (obj) -> pull.submitter = obj
       if pull.last_user
-        userPromises.push makeUserObj(pull.last_user).then (obj) -> pull.last_user = obj
+        promises.push getUserObj(pull.last_user).then (obj) -> pull.last_user = obj

       for username in pull.reviewers
-        userPromises.push makeUserObj(username).then (obj) -> pull.reviewers.push obj
-      pull.reviewers = []
-
-      return Q(pull)
+        promises.push getUserObj(username).then (obj) -> pull.reviewers.push obj
+      pull.reviewers = [] # Above callbacks happen after this.
+
+      return Q.all(promises).thenResolve(pull)

   # Annotate a bunch of pulls.
   annotatePulls = (pulls) ->


Revert "Temporary debug mode"

Revert "Temporary debug mode"

This reverts commit 0f93f4e26ca02c1eab74b7359641104895fd068b.

--- prviewer.coffee
+++ prviewer.coffee
@@ -124,7 +124,6 @@
 app.get '/', ensureAuthenticated, (req, res) ->
   github = new GitHubAPI(
     version: '3.0.0'
-    debug: true
     customHeaders:
       # Needed for the new GitHub API
       'User-Agent': 'pull-request-viewer'


Moved notify function out and do not notify before entering the game

--- views/components/main/parts/kdock-panel.cjsx
+++ views/components/main/parts/kdock-panel.cjsx
@@ -21,10 +21,6 @@
     else if timeRemaining is 0
       style = 'success'
     @setState {style: style} if style isnt @state.style
-  notify: ->
-    notify "#{@props.dockName} 建造完成",
-      type: 'construction'
-      icon: join(ROOT, 'assets', 'img', 'operation', 'build.png')
   render: ->

     {
@@ -32,7 +28,7 @@
         <CountdownTimer countdownId={"kdock-#{@props.dockIndex}"}
                         completeTime={@props.completeTime}
                         tickCallback={@tick}
-                        completeCallback={@notify} />
+                        completeCallback={@props.notify} />
     }

@@ -69,7 +65,10 @@
   path = join(ROOT, 'assets', 'img', 'material', "0#{idx}.png")

+constructionIcon = join(ROOT, 'assets', 'img', 'operation', 'build.png')
+
 KdockPanel = React.createClass
+  canNotify: false
   getInitialState: ->
     docks: new Array(5).fill(0).map () -> new KDockInfo
   handleResponse: (e) ->
@@ -77,6 +76,11 @@
     {$ships} = window
     {docks} = @state
     switch path
+      when '/kcsapi/api_start2'
+        # Do not notify before entering the game
+        @canNotify = false
+      when '/kcsapi/api_port/port'
+        @canNotify = true
       when '/kcsapi/api_get_member/kdock', '/kcsapi/api_req_kousyou/getship'
         kdocks = body
         kdocks = body.api_kdock if path is '/kcsapi/api_req_kousyou/getship'
@@ -97,6 +101,15 @@
     window.addEventListener 'game.response', @handleResponse
   componentWillUnmount: ->
     window.removeEventListener 'game.response', @handleResponse
+  notify: ->
+    return if not @canNotify
+    completedShips = []
+    for i in [1..4]
+      if 0 <= @state.docks[i].completeTime <= new Date().getTime()
+        completedShips.push i18n.resources.__ @state.docks[i].name
+    notify "#{completedShips.join ', '} #{__ 'built'}",
+      type: 'construction'
+      icon: constructionIcon
   render: ->

     {
@@ -110,7 +123,7 @@
                                     dockIndex={i}
                                     completeTime={@state.docks[i].completeTime}
                                     isLSC={isLSC}
-                                    dockName={dockName} />
+                                    notify={@notify} />

         if isInUse


Fix discussion tabs label

Fix discussion tabs label

--- resources/assets/coffee/react/beatmap-discussions/mode-switcher.coffee
+++ resources/assets/coffee/react/beatmap-discussions/mode-switcher.coffee
@@ -72,7 +72,7 @@
                             span
                               className: 'page-mode-link__subtitle'
                               key: 'scope'
-                              "(#{osu.trans('beatmaps.discussions.mode.scopes.general')})"
+                              "(#{osu.trans('beatmaps.discussions.mode.scopes.generalAll')})"

                     else
                       osu.trans("beatmaps.discussions.mode.#{snakeCase mode}")


fix cursor position on redraw

fix cursor position on redraw

--- app/graph.coffee
+++ app/graph.coffee
@@ -124,6 +124,7 @@
     legend = d3.select(container).append('ul')
       .attr('class', 'legend')

+    currentCrosshairTime = null

     destroyFunctions = []
     destroy = ->
@@ -342,6 +343,7 @@
           .attr('fill', '#efefef')

       positionCrosshair = (x, time) ->
+        currentCrosshairTime = time
         vertical_crosshair
           .attr('x1', x)
           .attr('x2', x)
@@ -367,13 +369,20 @@
         positionCrosshair p.x, p.time
         cursorBus.push p.time

-      destroyFunctions.push externalCursorChanges.onValue (time) ->
+      boundedPositionCrosshair = (time) ->
         domain = x.domain()
         if time < domain[0]
           time = domain[0]
         else if time > domain[1]
           time = domain[1]
         positionCrosshair x(time), time
+
+      if currentCrosshairTime?
+        boundedPositionCrosshair(currentCrosshairTime)
+      # TODO if the mouse is over the graph, use that position so it doesn't jump
+
+      destroyFunctions.push externalCursorChanges.onValue (time) ->
+        boundedPositionCrosshair(time)

       target = g.selectAll('.target')
           .data(targets)


append to p#hint_sentence with example sentence

--- cf/spell.coffee
+++ cf/spell.coffee
@@ -27,6 +27,7 @@
     word = random_question(data)
     update_keyHandler()
     $("p#hint_define").empty().append(word['hint'])
+    $("p#hint_sentence").empty().append(word['example'])

   update_keyHandler = () ->


Modification of delta pinch/spread

Modification of delta pinch/spread

--- Analyzer.coffee
+++ Analyzer.coffee
@@ -385,8 +385,8 @@
 		[].concat permutations...

 window.onload = ->
-	$("blue").bind "rotation:cw", (event) ->
-		$('debug').innerHTML = "rotate cw" + "
" + $('debug').innerHTML
+	$("blue").bind "tap", (event) ->
+		$('debug').innerHTML = "tap" + "
" + $('debug').innerHTML
 ###
 		if name.contains "flick"
 			$('debug').innerHTML = event.global.type + "
"


update unit tests for duplicate checks

update unit tests for duplicate checks

--- services/web/test/unit/coffee/Project/ProjectEntityHandlerTests.coffee
+++ services/web/test/unit/coffee/Project/ProjectEntityHandlerTests.coffee
@@ -62,7 +62,7 @@
 		@ProjectGetter =
 			getProjectWithOnlyFolders : (project_id, callback)=> callback(null, @project)
 			getProjectWithoutDocLines : (project_id, callback)=> callback(null, @project)
-			getProject:sinon.stub()
+			getProject: sinon.stub().callsArgWith(2, null, @project)
 		@projectUpdater = markAsUpdated:sinon.stub()
 		@projectLocator =
 			findElement : sinon.stub()
@@ -287,6 +287,8 @@
 				@projectLocator.findElement = sinon.stub()
 				@projectLocator.findElement.withArgs({project: @project, element_id: @docId, type: 'docs'})
 					.callsArgWith(1, null, @doc, @path)
+				@projectLocator.findElement.withArgs({project: @project, element_id: folder_id, type:"folder"},)
+					.callsArgWith(1, null, @destFolder, @destFolderPath)
 				@ProjectEntityHandler.moveEntity project_id, @docId, folder_id, "docs", userId, done

 			it 'should find the doc to move', ->
@@ -314,6 +316,46 @@
 						rev: @doc.rev
 					})
 					.should.equal true
+
+		describe "moving a doc when another with the same name already exists", ->
+			beforeEach () ->
+				@docId = "4eecaffcbffa66588e000009"
+				@doc = { name: "another-doc.tex", lines:["1234","312343d"], rev: "1234"}
+				@path = {
+					mongo:"folders[0]"
+					fileSystem:"/old_folder/somewhere.txt"
+				}
+				@destFolder = { name: "folder", docs: [ {name:"another-doc.tex"} ] }
+				@destFolderPath = {
+					mongo:      "folders[0]"
+					fileSystem: "/dest_folder"
+				}
+				@projectLocator.findElement = sinon.stub()
+				@projectLocator.findElement.withArgs({project: @project, element_id: @docId, type: 'docs'})
+					.callsArgWith(1, null, @doc, @path)
+				@projectLocator.findElement.withArgs({project: @project, element_id: folder_id, type:"folder"},)
+					.callsArgWith(1, null, @destFolder, @destFolderPath)
+				@callback = sinon.stub()
+				@ProjectEntityHandler.moveEntity project_id, @docId, folder_id, "docs", userId, @callback
+
+			it 'should return an error', ->
+				@callback.calledWith(new Errors.InvalidNameError("file already exists")).should.equal true
+
+			it "should should not send the update to the doc updater", ->
+				@documentUpdaterHandler.updateProjectStructure
+					.called.should.equal false
+
+			it 'should not remove the element from its current position', ->
+				@ProjectEntityHandler._removeElementFromMongoArray
+					.called.should.equal false
+
+			it "should not put the element back in the new folder", ->
+				@ProjectEntityHandler._putElement.called.should.equal false
+
+			it 'should not tell the third party data store', ->
+				@tpdsUpdateSender.moveEntity
+					.called.should.equal false
+

 		describe "moving a folder", ->
 			beforeEach ->
@@ -379,10 +421,13 @@
 						})
 						.should.equal true

-			describe "when the destination folder is inside the moving folder", ->
+			describe "when the destination folder contains a file with the same name", ->
 				beforeEach ->
-					@path.fileSystem = "/one/two"
-					@pathToMoveTo.fileSystem = "/one/two/three"
+					@path.fileSystem = "/one/src_dir"
+					@pathToMoveTo.fileSystem = "/two/dest_dir"
+					@folder_to_move_to = { name: "folder to move to", fileRefs: [ {name: "folder"}] }
+					@projectLocator.findElement.withArgs({project: @project, element_id: @move_to_folder_id, type: 'folder'})
+						.callsArgWith(1, null, @folder_to_move_to, @pathToMoveTo)
 					@callback = sinon.stub()
 					@ProjectEntityHandler.moveEntity project_id, @folder_id, @move_to_folder_id, "folder", userId, @callback

@@ -397,6 +442,30 @@

 				it "should return an error", ->
 					@callback
+						.calledWith(new Errors.InvalidNameError("file already exists"))
+						.should.equal true
+
+			describe "when the destination folder is inside the moving folder", ->
+				beforeEach ->
+					@path.fileSystem = "/one/two"
+					@pathToMoveTo.fileSystem = "/one/two/three"
+
+					@projectLocator.findElement.withArgs({project: @project, element_id: @move_to_folder_id, type: 'folder'})
+						.callsArgWith(1, null, @folder_to_move_to, @pathToMoveTo)
+					@callback = sinon.stub()
+					@ProjectEntityHandler.moveEntity project_id, @folder_id, @move_to_folder_id, "folder", userId, @callback
+
+				it 'should find the folder we are moving to element', ->
+					@projectLocator.findElement
+						.calledWith({
+							element_id: @move_to_folder_id,
+							type: "folder",
+							project: @project
+						})
+						.should.equal true
+
+				it "should return an error", ->
+					@callback
 						.calledWith(new Error("destination folder is a child folder of me"))
 						.should.equal true

@@ -472,6 +541,7 @@
 			@lines = ['1234','abc']
 			@path = "/path/to/doc"

+			@ProjectGetter.getProject = sinon.stub().callsArgWith(2, null, @project)
 			@ProjectEntityHandler._putElement = sinon.stub().callsArgWith(4, null, {path:{fileSystem:@path}})
 			@callback = sinon.stub()
 			@tpdsUpdateSender.addDoc = sinon.stub().callsArg(1)
@@ -522,6 +592,7 @@
 			@lines = ['1234','abc']
 			@path = "/path/to/doc"

+			@ProjectGetter.getProject = sinon.stub().callsArgWith(2, null, @project)
 			@ProjectEntityHandler._putElement = sinon.stub().callsArgWith(4, null, {path:{fileSystem:@path}})
 			@callback = sinon.stub()
 			@tpdsUpdateSender.addDoc = sinon.stub().callsArg(1)
@@ -1123,6 +1194,20 @@
 			@newName = "new.tex"
 			@path = mongo: "mongo.path", fileSystem: "/oldnamepath/oldname"

+			@project_id = project_id
+			@project =
+				_id: ObjectId(project_id)
+				rootFolder: [_id:ObjectId()]
+			@folder =
+				_id: ObjectId()
+				name: "someFolder"
+				docs: [ {name: "another-doc.tex"} ]
+				fileRefs: [ {name: "another-file.tex"} ]
+				folders: [ {name: "another-folder"} ]
+			@doc =
+				_id: ObjectId()
+				name: "new.tex"
+
 			@ProjectGetter.getProject.callsArgWith(2, null, @project)
 			@ProjectEntityHandler.getAllEntitiesFromProject = sinon.stub()
 			@ProjectEntityHandler.getAllEntitiesFromProject
@@ -1132,7 +1217,7 @@
 				.onSecondCall()
 				.callsArgWith(1, null, @newDocs = ['new-doc'], @newFiles = ['new-file'])

-			@projectLocator.findElement = sinon.stub().callsArgWith(1, null, @entity = { _id: @entity_id, name:"oldname", rev:4 }, @path)
+			@projectLocator.findElement = sinon.stub().callsArgWith(1, null, @entity = { _id: @entity_id, name:"oldname", rev:4 }, @path, @folder)
 			@tpdsUpdateSender.moveEntity = sinon.stub()
 			@ProjectModel.findOneAndUpdate = sinon.stub().callsArgWith(3, null, @project)
 			@documentUpdaterHandler.updateProjectStructure = sinon.stub().yields()
@@ -1153,6 +1238,27 @@
 			@ProjectEntityHandler.renameEntity project_id, @entity_id, @entityType, @newName, userId, =>
 				@tpdsUpdateSender.moveEntity.calledWith({project_id:project_id, startPath:@path.fileSystem, endPath:"/oldnamepath/new.tex", project_name:@project.name, rev:4}).should.equal true
 				done()
+
+		describe "when a document already exists with the same name", ->
+			beforeEach ->
+				@project =
+					_id: ObjectId(project_id)
+					rootFolder: [_id:ObjectId()]
+				@folder =
+					_id: ObjectId()
+					name: "someFolder"
+					docs: [ {name: "another-doc.tex"} ]
+					fileRefs: [ {name: "another-file.tex"} ]
+					folders: [ {name: "another-folder"} ]
+				@doc =
+					_id: ObjectId()
+					name: "new.tex"
+				@newName = "another-doc.tex"
+
+			it "should return an error", (done)->
+				@ProjectEntityHandler.renameEntity project_id, @entity_id, @entityType, @newName, userId, (err)=>
+					err.should.deep.equal new Errors.InvalidNameError("file already exists")
+					done()

 	describe "_insertDeletedDocReference", ->
 		beforeEach ->
@@ -1248,6 +1354,9 @@
 			@folder =
 				_id: ObjectId()
 				name: "someFolder"
+				docs: [ {name: "another-doc.tex"} ]
+				fileRefs: [ {name: "another-file.tex"} ]
+				folders: [ {name: "another-folder"} ]
 			@doc =
 				_id: ObjectId()
 				name: "new.tex"
@@ -1288,6 +1397,33 @@
 					name:"something"
 				@ProjectEntityHandler._putElement @project, @folder._id, doc, "doc", (err)=>
 					@ProjectModel.findOneAndUpdate.called.should.equal false
+					done()
+
+			it "should error if a document already exists with the same name", (done)->
+				doc =
+					_id: ObjectId()
+					name: "another-doc.tex"
+				@ProjectEntityHandler._putElement @project, @folder, doc, "doc", (err)=>
+					@ProjectModel.findOneAndUpdate.called.should.equal false
+					err.should.deep.equal new Errors.InvalidNameError("file already exists")
+					done()
+
+			it "should error if a file already exists with the same name", (done)->
+				doc =
+					_id: ObjectId()
+					name: "another-file.tex"
+				@ProjectEntityHandler._putElement @project, @folder, doc, "doc", (err)=>
+					@ProjectModel.findOneAndUpdate.called.should.equal false
+					err.should.deep.equal new Errors.InvalidNameError("file already exists")
+					done()
+
+			it "should error if a folder already exists with the same name", (done)->
+				doc =
+					_id: ObjectId()
+					name: "another-folder"
+				@ProjectEntityHandler._putElement @project, @folder, doc, "doc", (err)=>
+					@ProjectModel.findOneAndUpdate.called.should.equal false
+					err.should.deep.equal new Errors.InvalidNameError("file already exists")
 					done()

 		describe "_countElements", ->


Major overhaul of the expression manipulation library

--- javascripts/lib/math/expression_manipulation.coffee
+++ javascripts/lib/math/expression_manipulation.coffee
@@ -9,8 +9,8 @@

     class ExpressionManipulation
       initialize: (opts={})->
-        @comps = opts.component_source
-
+        @comps = opts.comps
+        @pos = opts.pos
       evaluate: (exp)->
         expression_evaluation.build(exp).resultingExpression()

@@ -19,12 +19,12 @@
         if result then result.value() else 0

     class Calculate extends ExpressionManipulation
-      invoke: (expression)->
+      perform: (expression)->
         @evaluate(expression)
     class_mixer(Calculate)

     class Square extends ExpressionManipulation
-      invoke: (expression)->
+      perform: (expression)->
         val = @value(expression)
         @comps.build_expression expression: [@comps.build_number(value: val*val)]
     class_mixer(Square)
@@ -36,15 +36,22 @@
         if last instanceof @comps.classes.number
           new_last = last.clone()
           new_last = new_last.futureAsDecimal()
-          expression.replaceLast(new_last)
+          new_exp = expression.replaceLast(new_last)
         else
           new_last = @comps.build_number(value: 0)
           new_last = new_last.futureAsDecimal()
-          expression.append(new_last)
-
-      invoke: (expression)->
-        _FinalOpenSubExpressionApplication.build(expr: expression, comps: @comps).
-          invokeOrDefault((expr)=> @onFinalExpression(expr))
+          new_exp = expression.append(new_last)
+        @pointer = @pos.build(new_exp.last().id())
+        new_exp
+
+      perform: (expression)->
+        resulting_expression =
+          _FinalOpenSubExpressionApplication.build(expr: expression, comps: @comps).
+            performOrDefault((expr)=> @onFinalExpression(expr))
+
+        expression: resulting_expression
+        position: @pointer
+

     class_mixer(AppendDecimal)

@@ -52,23 +59,45 @@
       initialize: (opts={})->
         super
         @val = opts.value
-
-      doAppend: (expression)->
-        last = expression.last()
-        number_with_this_val = @comps.build_number(value: @val)
-
-        if last && last instanceof @comps.classes.number
-          new_last = last.concatenate(@val)
-          expression.replaceLast(new_last)
-        else if (last && last instanceof @comps.classes.exponentiation) or (last && !last.isOperator())
-          expression.append(@comps.build_multiplication()).
-            append(number_with_this_val)
-        else
-          expression.append(number_with_this_val)
-
-      invoke: (expression)->
-        _FinalOpenSubExpressionApplication.build(expr: expression, comps: @comps).
-          invokeOrDefault((expr)=> @doAppend(expr))
+      # doAppend: (expression)->
+      #   last = expression.last()
+      #   number_to_append = @comps.build_number(value: @val)
+
+      #   if last && last instanceof @comps.classes.number
+      #     new_last = last.concatenate(@val)
+      #     new_exp = expression.replaceLast(new_last)
+      #   else if (last && last instanceof @comps.classes.exponentiation) or (last && !last.isOperator())
+      #     new_exp = expression.append(@comps.build_multiplication()).
+      #       append(number_to_append)
+      #   else
+      #     new_exp = expression.append(number_to_append)
+
+      #   @pointer = @pos.build(position: new_exp.id())
+      #   new_exp
+
+      # @destructive
+      doAppendD: (expression, expression_position)->
+        number_to_append = @comps.build_number(value: @val)
+        last = expression.last()
+        if last
+          if last instanceof @comps.classes.number
+            expression.last().concatenateD(@val)
+          else if (last instanceof @comps.classes.exponentiation) or !last.isOperator()
+            expression.appendD(@comps.build_multiplication())
+            expression.appendD(number_to_append)
+          else
+            expression.appendD(number_to_append)
+        else
+          expression.appendD(number_to_append)
+
+      perform: (expression_position)->
+        result_exp = _ExpressionManipulator.build(expression_position.expression()).clone().
+          withComponent(expression_position, (component)=>
+            @doAppendD(component, expression_position)
+          ).value()
+
+        # would be same position, but a new expression
+        expression_position.clone(expression: result_exp)

     class_mixer(AppendNumber)

@@ -97,7 +126,7 @@
         else
           power.open()

-      invoke: (expression)->
+      perform: (expression)->
         last = expression.last()

         # in the first case, our base case comes
@@ -122,48 +151,71 @@

     class_mixer(ExponentiateLast)

+    class AppendExponentiation extends ExpressionManipulation
+      perform: (expression_position)->
+        base = @comps.build_expression()
+        power = @comps.build_expression()
+
+        result_exp = _ExpressionManipulator.build(expression_position.expression()).clone().
+          withComponent(expression_position, (component)=>
+
+            _ImplicitMultiplication.build(@comps).invokeD(component)
+            component.appendD(@comps.build_exponentiation(base: base, power: power))
+          ).value()
+
+        # would be same position, but a new expression
+        expression_position.clone(expression: result_exp)
+
+    class_mixer(AppendExponentiation)
+
     class AppendMultiplication extends ExpressionManipulation
       appendAction: (expression)->
-        _OverrideIfOperatorOrAppend.build(@comps, expression).with @comps.build_multiplication()
-
-      invoke: (expression)->
-        _FinalOpenSubExpressionApplication.build(expr: expression, comps: @comps).
-          invokeOrDefault((expr)=> @appendAction(expr))
+        new_exp = _OverrideIfOperatorOrAppend.build(@comps, expression).with @comps.build_multiplication()
+        @position = @pos.build(new_exp.last().id())
+        new_exp
+
+      perform: (expression, position)->
+        if expression.isEmpty()
+          expression: expression
+          position: position
+        else
+          expr = _FinalOpenSubExpressionApplication.build(expr: expression, comps: @comps).
+            performOrDefault((expr)=> @appendAction(expr))
+          expression: expr
+          position: @position

     class_mixer(AppendMultiplication)

     class AppendEquals extends ExpressionManipulation
-      invoke: (expression)->
+      perform: (expression)->
         expression.append @comps.build_equals()
     class_mixer(AppendEquals)

     class AppendDivision extends ExpressionManipulation
-      invoke: (expression)->
+      perform: (expression)->
         _FinalOpenSubExpressionApplication.build(expr: expression, comps: @comps).
-          invokeOrDefault((expr)=>
+          performOrDefault((expr)=>
             _OverrideIfOperatorOrAppend.build(@comps, expr).with @comps.build_division())
     class_mixer(AppendDivision)

     class AppendAddition extends ExpressionManipulation
-      invoke: (expression)->
+      perform: (expression)->
         _FinalOpenSubExpressionApplication.build(expr: expression, comps: @comps).
-          invokeOrDefault((expression)->
+          performOrDefault((expression)->
             _OverrideIfOperatorOrAppend.build(@comps, expression).with @comps.build_addition())

     class_mixer(AppendAddition)

-
     class AppendSubtraction extends ExpressionManipulation
-      invoke: (expression)->
+      perform: (expression)->
         _FinalOpenSubExpressionApplication.build(expr: expression, comps: @comps).
-          invokeOrDefault((expression)=>
+          performOrDefault((expression)=>
             _OverrideIfOperatorOrAppend.build(@comps, expression).with @comps.build_subtraction())
-
     class_mixer(AppendSubtraction)

     class Negation extends ExpressionManipulation
-      invoke: (expression)->
+      perform: (expression)->
         last = expression.last()
         if last
             expression.replaceLast(last.negated())
@@ -172,18 +224,108 @@

     class_mixer(Negation)

-    class OpenSubExpression extends ExpressionManipulation
+    class AppendOpenSubExpression extends ExpressionManipulation
       action: (expression)->
+        new_exp = @comps.build_expression().open()
+        @pointer = @pos.build(position: new_exp.id())
         _ImplicitMultiplication.build(@comps).
-          onNumeric(expression).
-          append(@comps.build_expression().open())
-
-      invoke: (expression)->
-        _FinalOpenSubExpressionApplication.build(expr: expression, comps: @comps).
-          invokeOrDefault((expression)=> @action(expression))
-
-
-    class_mixer(OpenSubExpression)
+          invoke(expression).
+          append(new_exp)
+
+      perform: (expression)->
+        exp = _FinalOpenSubExpressionApplication.build(expr: expression, comps: @comps).
+          performOrDefault((expression)=> @action(expression))
+        expression: exp
+        position: @pointer
+
+
+    class_mixer(AppendOpenSubExpression)
+
+    class CloseSubExpression extends ExpressionManipulation
+      perform: (expression)->
+        ret = _FinalOpenSubExpressionApplication.build(expr: expression, comps: @comps).
+          perform((expression)-> expression.close())
+        ret
+    class_mixer(CloseSubExpression)
+
+    # TODO this needs to ahve final open sub expression application
+    class AppendPi extends ExpressionManipulation
+      perform: (expression)->
+        _ImplicitMultiplication.build(@comps).
+          invoke(expression).
+          append(@comps.build_pi())
+
+    class_mixer(AppendPi)
+
+    class AppendRoot extends ExpressionManipulation
+      initialize: (opts={})->
+        super
+        @degree = opts.degree
+
+      perform: (expression_position)->
+        degree =
+          if @degree
+            @comps.build_expression expression: [
+              @comps.build_number(value: @degree)
+            ]
+          else
+            @comps.build_expression()
+        radicand = @comps.build_expression(expression: [])
+        root = @comps.build_root(degree: degree, radicand: radicand)
+
+        result_exp = _ExpressionManipulator.build(expression_position.expression()).clone().
+          withComponent(expression_position, (component)=>
+            _ImplicitMultiplication.build(@comps).invokeD(component)
+            component.appendD(root)
+          ).value()
+
+        # would be same position, but a new expression
+        expression_position.clone(expression: result_exp)
+
+    class_mixer(AppendRoot)
+
+    class AppendVariable extends ExpressionManipulation
+      initialize: (opts={})->
+        super
+        @variable_name = opts.variable
+      perform: (expression)->
+        variable = @comps.build_variable(name: @variable_name)
+        _ImplicitMultiplication.build(@comps).
+          invoke(expression).
+          append(variable)
+
+
+    class_mixer(AppendVariable)
+
+    class SquareRoot extends ExpressionManipulation
+      perform: (expression)->
+        value = @value(expression)
+        root = Math.sqrt(parseFloat(value))
+        unless isNaN(root)
+          num = @comps.build_number value: "#{root}"
+          @comps.classes.expression.buildWithContent [num]
+        else
+          @comps.classes.expression.buildError()
+    class_mixer(SquareRoot)
+
+
+    class Reset extends ExpressionManipulation
+      perform:  (expression, position)->
+        empty_expression = @comps.build_expression(expression: [])
+        @pos.build(expression: empty_expression, position: empty_expression.id())
+    class_mixer(Reset)
+
+    class UpdatePosition extends ExpressionManipulation
+      initialize: (opts={})->
+        super
+        @new_position_element_id = opts.element_id
+        @new_position_element_type = opts.type
+
+      perform:  (expression_position)->
+        expression_position.clone(position: @new_position_element_id, type: @new_position_element_type)
+
+    class_mixer(UpdatePosition)
+

     class _FinalOpenSubExpressionApplication
       initialize: (opts={})->
@@ -203,7 +345,7 @@
         else # not closable, not handled, return
           expr

-      invoke: (@action)->
+      perform: (@action)->
         @findAndPerformAction(@expr)

       wasFound: -> @found
@@ -225,7 +367,7 @@
           expr.radicand()
         else false

-      invokeOrDefault: (@action)->
+      performOrDefault: (@action)->
         result = @findAndPerformAction(@expr)
         if @wasFound()
           result
@@ -235,68 +377,20 @@
     class_mixer(_FinalOpenSubExpressionApplication)

-    class CloseSubExpression extends ExpressionManipulation
-      invoke: (expression)->
-        ret = _FinalOpenSubExpressionApplication.build(expr: expression, comps: @comps).
-          invoke((expression)-> expression.close())
-        ret
-    class_mixer(CloseSubExpression)
-
-    # TODO this needs to ahve final open sub expression application
-    class AppendPi extends ExpressionManipulation
-      invoke: (expression)->
-        _ImplicitMultiplication.build(@comps).
-          onNumeric(expression).
-          append(@comps.build_pi())
-
-    class_mixer(AppendPi)
-
-    class AppendRoot extends ExpressionManipulation
-      initialize: (opts={})->
-        super
-        @degree = opts.degree
-      invoke: (expression)->
-        degree = @comps.build_expression expression: [
-            @comps.build_number(value: @degree)
-          ]
-        radicand = @comps.build_expression(expression: []).open()
-        root = @comps.build_root(degree: degree, radicand: radicand)
-
-        _ImplicitMultiplication.build(@comps).
-          onNumeric(expression).
-          append(root)
-    class_mixer(AppendRoot)
-
-    class AppendVariable extends ExpressionManipulation
-      initialize: (opts={})->
-        super
-        @variable_name = opts.variable
-      invoke: (expression)->
-        variable = @comps.build_variable(name: @variable_name)
-        _ImplicitMultiplication.build(@comps).
-          onNumeric(expression).
-          append(variable)
-
-
-    class_mixer(AppendVariable)
-
-    class SquareRoot extends ExpressionManipulation
-      invoke: (expression)->
-        value = @value(expression)
-        root = Math.sqrt(parseFloat(value))
-        unless isNaN(root)
-          num = @comps.build_number value: "#{root}"
-          @comps.classes.expression.buildWithContent [num]
-        else
-          @comps.classes.expression.buildError()
-    class_mixer(SquareRoot)
-
     class _ImplicitMultiplication
       initialize: (@comps)->
-      onNumeric: (expression)->
+      invoke: (expression)->
         last = expression.last()
         if last && (last.isNumber() || last instanceof @comps.classes.expression || last instanceof @comps.classes.pi)
           expression.append(@comps.build_multiplication())
+        else
+          expression
+
+      invokeD: (expression)->
+        last = expression.last()
+        if last && (last.isNumber() || last instanceof @comps.classes.expression || last instanceof @comps.classes.pi)
+          expression.appendD(@comps.build_multiplication())
+          expression
         else
           expression

@@ -328,34 +422,53 @@

     class_mixer(_TrailingOperatorHandling)

+    class _ExpressionManipulator
+      initialize: (@expr)->
+      clone: ->
+        @expr = @expr.clone()
+        @
+      withComponent: (position, fn)->
+        comp = ttm.lib.math.ExpressionTraversal.build(@expr).
+          findForID(position.position())
+        fn(comp)
+        @
+
+      value: -> @expr
+
+    class_mixer(_ExpressionManipulator)
+
     exports =
       calculate: Calculate
       square: Square
       append_decimal: AppendDecimal
       append_number: AppendNumber
       exponentiate_last: ExponentiateLast
+      append_exponentiation: AppendExponentiation
       append_multiplication: AppendMultiplication
       append_addition: AppendAddition
       append_equals: AppendEquals
       append_subtraction: AppendSubtraction
       negate_last: Negation
-      open_sub_expression: OpenSubExpression
+      append_open_sub_expression: AppendOpenSubExpression
       close_sub_expression: CloseSubExpression
       append_division: AppendDivision
       append_pi: AppendPi
+      update_position: UpdatePosition
       square_root: SquareRoot
       append_root: AppendRoot
       append_variable: AppendVariable
+      reset: Reset

     class ExpressionManipulationSource
-      initialize: (@comps)->
+      initialize: (@comps, @pos)->
       classes: exports
     ttm.class_mixer(ExpressionManipulationSource)

     for name, klass of exports
       build_klass = do (name, klass)->
         (opts={})->
-          opts.component_source = @comps
+          opts.comps = @comps
+          opts.pos = @pos
           klass.build(opts)
       ExpressionManipulationSource.prototype["build_#{name}"] = build_klass


EDSC-501: Update Jasmine specs to reflect more accurate area calculations

--- spec/javascripts/modules/geoutilSpec.coffee
+++ spec/javascripts/modules/geoutilSpec.coffee
@@ -18,12 +18,12 @@
       expect(area(lls [[0, 175], [0, -175], [10, 175]])).toBeCloseTo(0.015, 2)

     it 'determines correct polygon interior area for polygons containing the north pole', ->
-      expect(area(lls [[85, 0], [85, 120], [85, -120]])).toBeCloseTo(0.02, 2)
-      expect(area(lls [[-85, 120], [-85, -120], [-85, 0]])).toBeCloseTo(12.54, 2)
+      expect(area(lls [[85, 0], [85, 120], [85, -120]])).toBeCloseTo(0.015, 2)
+      expect(area(lls [[-85, 120], [-85, -120], [-85, 0]])).toBeCloseTo(12.55, 2)

     it 'determines correct polygon interior area for polygons containing the south pole', ->
-      expect(area(lls [[-85, 0], [-85, -120], [-85, 120]])).toBeCloseTo(0.02, 2)
-      expect(area(lls [[85, -120], [85, 120], [85, 0]])).toBeCloseTo(12.54, 2)
+      expect(area(lls [[-85, 0], [-85, -120], [-85, 120]])).toBeCloseTo(0.015, 2)
+      expect(area(lls [[85, -120], [85, 120], [85, 0]])).toBeCloseTo(12.55, 2)

     it 'determines correct polygon interior area for polygons containing the both poles', ->
       expect(area(lls [[0, 0], [10, 0], [0, 10]])).toBeCloseTo(12.55, 2)
@@ -35,4 +35,4 @@
       expect(area(lls [[-85, 0], [-85, -120], [-90, 0]])).toBeCloseTo(0.004, 2)

     it 'determines correct polygon interior area for polygons touching the both poles', ->
-      expect(area(lls [[0, 5], [90, 0], [0, -5], [-90, 0]])).toBeCloseTo(0.17, 2)
+      expect(area(lls [[0, 5], [90, 0], [0, -5], [-90, 0]])).toBeCloseTo(0.30, 2)


re-kerjigger console logging of LP request status for now and more importantly ensure that we only call the base Transport class's _open method upon first successful request

--- lib/assets/javascripts/firehose/long_poll.js.coffee
+++ lib/assets/javascripts/firehose/long_poll.js.coffee
@@ -53,9 +53,8 @@
     $.ajax @config.longPoll.url, opts

   _xhrComplete: (jqXhr) =>
-    console?.log "XHR complete", arguments
+    console?.log "XHR complete", "status #{jqXhr.status}", arguments
     # Get the last sequence from the server if specified.
-    console?.log "status #{jqXhr.status}"
     if jqXhr.status == 200
       val = jqXhr.getResponseHeader @messageSequenceHeader
       @_lastMessageSequence = val if val?
@@ -68,10 +67,9 @@
     @_stopRequestLoop = true

   _success: (data, status, jqXhr) =>
-    console?.log "_success", arguments
-    @_open data
+    console?.log "_success", "status #{jqXhr.status}", arguments
+    @_open data unless @_succeeded
     return if @_stopRequestLoop
-    console?.log "status #{jqXhr.status}"
     if jqXhr.status == 204
       # If we get a 204 back, that means the server timed-out and sent back a 204 with a
       # X-Http-Next-Request header


move corner up

move corner up

--- src/config/controls.coffee
+++ src/config/controls.coffee
@@ -5,8 +5,8 @@
   row: 0
   col: 0
   i: (world, args) ->
-    world.get('split')+0.5
-  j: 0.5
+    world.get('split')+0.25
+  j: 0.25
   path: (world, args) ->
     scale = world.get('scale') / 2.5
     half = scale / 2


<tests> Rule - Adding fixture for update.

--- tests/fixtures/rule.coffee
+++ tests/fixtures/rule.coffee
@@ -8,6 +8,9 @@
 module.exports =
 	rule1:
 		name: "Rule #1"
+
+	updateRule1:
+		name: "Rule #1 v2"

 	actuatorRuleSayHello:
 		ruleId:		null


Hotfix anonymizing peer graders

Hotfix anonymizing peer graders

Warning, this approach is pretty heavy handed. Should be made
to be be precisely the right file.

--- src/smc-webapp/editor_course.cjsx
+++ src/smc-webapp/editor_course.cjsx
@@ -791,6 +791,18 @@
                             delete_missing    : assignment.get('delete_missing')
                             exclude_history   : true
                             cb                : cb
+                    (cb) =>
+                        if peer_graded
+                            # Delete GRADER file
+                            salvus_client.exec
+                                project_id : student_project_id
+                                command    : 'rm ./*/GRADER*'
+                                timeout    : 60
+                                bash       : true
+                                path       : assignment.get('graded_path')
+                                cb         : cb
+                          else
+                              cb(null)
                 ], finish)

         # Copy the given assignment to all non-deleted students, doing several copies in parallel at once.


Add integration test for listing files in large directories

--- src/spec/helpers/sftp.spec.coffee
+++ src/spec/helpers/sftp.spec.coffee
@@ -100,6 +100,18 @@
     .catch (error) -> done(error)
     .done()

+  it 'should list files in large directories', (done) ->
+    Promise.map [1..100], (i) =>
+      @helpers.safePutFile @_sftp, FILE_LOCAL, FOLDER_REMOTE + "/test#{i}.txt"
+    , {concurrency: 5}
+    .then =>
+      @_sftp.listFiles @_sftp, FOLDER_REMOTE
+    .then (files) ->
+      expect(_.size files).toEqual 100
+      done()
+    .catch (error) -> done(error)
+  , 30000 # 30sec
+
   it 'should get file stats', (done) ->
     @helpers.stats(@_sftp, FILE_REMOTE)
     .then (stat) ->


Only score grabbed blocks.

Only score grabbed blocks.

--- entity.coffee
+++ entity.coffee
@@ -195,6 +195,10 @@
     return unless target?
     body = @getBody()
     targetBody = target.getBody()
+    lol = targetBody.GetUserData()
+    lol["grabbed"] = true;
+
+    targetBody.SetU
     headOffset = [0, -4.2]
     physics = body.GetWorld().UserData
     [headOffX, headOffY] = physics.rotate(headOffset)
@@ -230,7 +234,10 @@
   doHitGoal: ->
     currentTime = unixTime()
     age = currentTime - @spawnTime
-    @callback(this) unless age < 0.4
+    body = @getBody()
+    if body.GetUserData()["grabbed"]?
+      @callback(this) unless age < 0.4
+

 class LimitedLifespanAdapter extends EntityAdapter
   constructor: (@lifespan, @callback, @next) ->


test for indicator fat route

test for indicator fat route

--- server/test/integration/api_indicator.coffee
+++ server/test/integration/api_indicator.coffee
@@ -12,6 +12,7 @@
 Theme = require('../../models/theme').model
 Indicator = require('../../models/indicator').model
 IndicatorData = require('../../models/indicator_data').model
+Page = require('../../models/page').model

 test('POST create', (done) ->
   data =
@@ -49,6 +50,59 @@

       done()
     )
+  )
+)
+
+test("GET /indicators/:id/fat returns the indicator with its nested page ", (done) ->
+  indicator = null
+  nestedPage = new Page()
+  toObjectWithNestedPageStub = sinon.stub(Indicator::, 'toObjectWithNestedPage', ->
+    Q.fcall(=>
+      object = @toObject()
+      object.page = nestedPage
+      object
+    )
+  )
+
+  Q.nfcall(
+    helpers.createIndicator
+  ).then( (createdIndicator) ->
+    indicator = createdIndicator
+
+    Q.nfcall(
+      request.get, {
+        url: helpers.appurl("api/indicators/#{indicator.id}/fat")
+        json: true
+      }
+    )
+  ).spread( (res, body) ->
+
+    try
+      assert.equal res.statusCode, 200,
+        "Expected the query to succeed"
+
+      reloadedIndicator = body
+      assert.equal reloadedIndicator._id, indicator.id,
+        "Expected the query to return the correct indicator"
+
+      assert.property reloadedIndicator, 'page',
+        "Expected the page attribute to be populated"
+
+      assert.ok toObjectWithNestedPageStub.calledOnce,
+        "Expected indicator.toObjectWithNestedPage to be called"
+
+      assert.equal reloadedIndicator.page._id, nestedPage.id,
+        "Expected the page attribute to be the right page"
+
+      done()
+    catch err
+      done(err)
+    finally
+      toObjectWithNestedPageStub.restore()
+
+  ).fail( (err) ->
+    toObjectWithNestedPageStub.restore()
+    done(err)
   )
 )


Projector now prompts user when it is about to shutdown and allows them to cancel the process.

--- tp6/src/script/views/projector_pane.coffee
+++ tp6/src/script/views/projector_pane.coffee
@@ -20,8 +20,8 @@
     #Tp.devices.projector.bind "autoOffCancel", @autoOffCancel
     this.bind "autoOffCancel", @autoOffCancel

-    @warningTimer = null
-    @offTimer = null
+    Tp.room.offTimer = null
+    Tp.room.warningTimer = null

   render: () ->
     ($.tmpl "projector-pane-template", {
@@ -66,26 +66,23 @@

   autoOff: (state) ->
     if state == "on"
-      console.log "Timer turning on"
+      #timer callbacks
       shutOff = ->
         Tp.devices.projector.state_set 'power', false
       warning = ->
         $('#auto-off').show()
-        @offTimer = setTimeout shutOff, 5000
+        Tp.room.offTimer = setTimeout shutOff, 60000

-      #@timer = setTimeout callback, 10800000 # Three hour timer
-      @warningTimer = setTimeout warning, 10000
+      Tp.room.warningTimer = setTimeout warning, 10740000
     else if state == "off"
-      if @warningTimer then clearTimeout(@warningTimer)
-      if @offTimer then clearTimeout(@offTimer)
+      if Tp.room.warningTimer then clearTimeout(Tp.room.warningTimer)
+      if Tp.room.offTimer then clearTimeout(Tp.room.offTimer)
       $('#auto-off').hide()

-  autoOffCancel: () =>
+  autoOffCancel: () ->
     console.log "This thing should work.. Cancelling shutoff"
-    warningTimer = Tp.projectorPane.warningTimer
-    offTimeer = Tp.projectorPane.offTimer
-    if warningTimer then clearTimeout(Tp.projectorPane.warningTimer)
-    if offTimer then clearTimeout(Tp.projectorPane.offTimer)
+    clearTimeout(Tp.room.warningTimer)
+    clearTimeout(Tp.room.offTimer)
     $('#auto-off').hide()
     Tp.projectorPane.autoOff("on")

@@ -146,5 +143,4 @@

   autoOffClicked: () ->
     console.log "Cancel button clicked"
-    #Tp.devices.projector.trigger "autoOffCancel"
     Tp.projectorPane.trigger "autoOffCancel"


Prevent accidental production-mode exception fall-through.

--- src/view/bindings/abstract_binding.coffee
+++ src/view/bindings/abstract_binding.coffee
@@ -170,7 +170,7 @@

         # If the filter exists, grab it; otherwise, bail.
         unless filter = Batman.Filters[filterName]
-          Batman.developer.error "Unrecognized filter '#{filterName}' in key \"#{@keyPath}\"!"
+          return Batman.developer.error "Unrecognized filter '#{filterName}' in key \"#{@keyPath}\"!"

         @filterFunctions.push filter


Focus will unlock the selection

Focus will unlock the selection

--- src/trix/editor_controller.coffee
+++ src/trix/editor_controller.coffee
@@ -32,7 +32,6 @@
     @textController.lockSelection()

   didHideToolbarDialog: ->
-    @textController.unlockSelection()
     @textController.focus()

   didUpdateAttribute: (attributeName, value) ->


Remove unused ElementSet methods

Remove unused ElementSet methods

--- src/turbolinks/element_set.coffee
+++ src/turbolinks/element_set.coffee
@@ -4,18 +4,8 @@
       element: element
       value: element.outerHTML

-  hasElement: (element) ->
-    @findElement(element)?
-
-  findElement: (needleElement) ->
-    @getElementIndex()[needleElement.outerHTML]
-
   selectElementsMatchingSelector: (selector) ->
     elements = (element for {element, value} in @elements when Turbolinks.match(element, selector))
-    new @constructor elements
-
-  rejectElementsMatchingSelector: (selector) ->
-    elements = (element for {element, value} in @elements when not Turbolinks.match(element, selector))
     new @constructor elements

   getElementsNotPresentInSet: (elementSet) ->


Disable tests that rely on the chronically broken Angular testing nonsense.

--- src/volume/volume.e2e-spec.coffee
+++ src/volume/volume.e2e-spec.coffee
@@ -1,5 +1,7 @@
+_ = require('lodash')
+promiseRetry = require('../testing/promise-retry')
+
 expect = require('../testing/expect')()
-
 Page = require '../testing/page'

 ###*
@@ -17,8 +19,8 @@
   imagePanel: ->
     @find('qi-volume-image')

-  # The volume slider-player combo.
-  volumeChooser: ->
+  # The time point slider-player combo.
+  timePointChooser: ->
     @find('.qi-volume-chooser-left')

   # The session slider-player combo.
@@ -59,55 +61,73 @@
         expect(help, 'The {qu,sugg}estion box hyperlink is missing')
           .to.eventually.include(Page.SUGGESTION_BOX_URL)

-  describe 'Image Display', ->
+  # Since the image display is opaque to Protractor, we can
+  # only test its presence.
+  #
+  # Untestable because of the load delay. The retry business below
+  # doesn't work either.
+  #
+  # TODO - revisit in 2017 after Angular 2 testing is clarified (as if).
+  #   See also tests below.
+  xdescribe 'Image Display', ->
     pane = null

     beforeEach ->
-      pane = page.imagePanel()
+      # Wrap the panel resolver in a retry loop, since it might
+      # take a while for the image to load and the pane to display.
+      # Furthermore, wrap the retry argument in a function, since
+      # the then loses the self context.
+      pane = promiseRetry(-> page.imagePanel())

     it 'should display the image', ->
       expect(pane, 'The image panel is missing').to.eventually.exist

   # Verify the volume chooser heading. We can only detect the
   # volume slider and player.
-  describe.only 'Volume Chooser', ->
+  #
+  # TODO - see ImageDisplay TODO.
+  xdescribe 'Time Point Chooser', ->
     chooser = null

     beforeEach ->
-      page.volumeChooser().then (resolved) ->
+      page.timePointChooser().then (resolved) ->
         chooser = resolved

     it 'should display the volume chooser', ->
-      expect(chooser, 'The volume chooser is missing').to.exist
+      expect(chooser, 'The chooser is missing').to.exist

     it 'should display the volume chooser heading', ->
       # The heading is the first row.
       heading = chooser.findAll('.row').then (rows) ->
         rows[0].text()
-      expect(heading, 'The volume chooser heading is missing')
+      expect(heading, 'The chooser heading is missing')
         .to.eventually.exist
-      expect(heading, 'The volume chooser heading is incorrect')
+      expect(heading, 'The chooser heading is incorrect')
         .to.eventually.equal('Time Point')

     it 'should display the volume chooser slider', ->
       slider = chooser.find('.qi-vertical-slider')
-      expect(slider, 'The volume chooser slider is missing')
+      expect(slider, 'The chooser slider is missing')
         .to.eventually.exist

     it 'should display the volume chooser player', ->
-      player = chooser.find('qi-player')
-      expect(player, 'The player is missing')
-        .to.eventually.exist
-      player.findAll('button').then (buttons) ->
-        expect(buttons, 'The player buttons are missing')
-          .to.not.be.empty
-        expect(buttons.length, 'The player buttons count is incorrect')
-          .to.equal(3)
-        [previous, play, next] = buttons
-
+      chooser.find('.qi-volume-player').then (player) ->
+        expect(player, 'The player is missing').to.exist
+        player.findAll('button').then (buttons) ->
+          expect(buttons, 'The player buttons are missing')
+            .to.not.be.empty
+          expect(buttons.length, 'The player buttons count is incorrect')
+            .to.equal(3)
+          [previous, play, next] = buttons
+          next.click().then ->
+            expect(page.billboard, 'The next button did not change the billboard')
+              .to.eventually.equal('Breast Patient 1 Session 1 Scan 1 Volume 2')
+            previous.click().then ->
+              expect(page.billboard, 'The previous button did not change the billboard')
+                .to.eventually.equal('Breast Patient 1 Session 1 Scan 1 Volume 1')

-  # TODO - Verify the volume chooser heading in the same manner as the
-  # Volume Chooser.
+  # TODO - see ImageDisplay TODO.
+  # TODO - flush out once previous TODO is addressed.
   xdescribe 'Session Chooser', ->
     chooser = null


Add in i18n defaults

Add in i18n defaults

--- services/web/config/settings.defaults.coffee
+++ services/web/config/settings.defaults.coffee
@@ -105,7 +105,7 @@

 	# Where your instance of ShareLaTeX can be found publically. Used in emails
 	# that are sent out, generated links, etc.
-	siteUrl : 'http://localhost:3000'
+	siteUrl : siteUrl = 'http://localhost:3000'

 	# cooke domain
 	# use full domain for cookies to only be accesabble from that domain,
@@ -144,7 +144,9 @@
 	# ------
 	#
 	i18n:
-		defaultLng:"en-US"
+		subdomainLang:
+			www: {lngCode:"en", url: siteUrl}
+		defaultLng: "en"

 	# Spelling languages
 	# ------------------


Update default raven version to 1.1.22

Update default raven version to 1.1.22

--- services/web/config/settings.defaults.coffee
+++ services/web/config/settings.defaults.coffee
@@ -216,7 +216,7 @@
 	#   publicDSN: ""
 	#
 	# src should be either a remote url like
-	#    //cdn.ravenjs.com/1.1.16/jquery,native/raven.min.js
+	#    //cdn.ravenjs.com/1.1.22/jquery,native/raven.min.js
 	# or a local file in the js/libs directory.
 	# The publicDSN is the token for the client-side getSentry service.


Remove unused gulp-util dependencies from error.coffee

--- config/errors.coffee
+++ config/errors.coffee
@@ -1,6 +1,5 @@
 # Error handling setup
 # See: http://www.artandlogic.com/blog/2014/05/error-handling-in-gulp/
-gutil     = require 'gulp-util'

 fatalLevel = require('yargs').argv.fatal

@@ -14,8 +13,8 @@

 # Log error and kill process
 handleError = (level, error) ->
-  gutil.log error.message
-  gutil.log "Level: " + level + " isFatal" + isFatal(level)
+  #gutil.log error.message
+  #gutil.log "Level: " + level + " isFatal" + isFatal(level)
   process.exit(1) if isFatal(level)

 # Determine if the error is fatal. Returns true if the given level is


Added static can_destroy? to static Comment, to validate CommentPopoverView working

--- core/app/backbone/models/discussion.coffee
+++ core/app/backbone/models/discussion.coffee
@@ -12,7 +12,7 @@

   getComments: ->
     # TODO: this should be dynamicz
-    comment1 = new Comment id: 1, content: "Hoi doei Henk"
+    comment1 = new Comment id: 1, "can_destroy?": true, content: "I think of boredom as a clock. Every second that someone on my team is bored, a second passes on this clock. After some aggregated amount of seconds that varies for every person, they look at the time, throw up their arms, and quit."
     comment2 = new Comment id: 2, content: "Richie Hawtin - Live @ Amsterdam Dance Event"

     new Backbone.Collection [comment1, comment2]


Disable annoying log

Disable annoying log

--- server/app/bongo/abstractions/notifying.coffee
+++ server/app/bongo/abstractions/notifying.coffee
@@ -27,7 +27,7 @@
       receiver?.sendNotification? event, contents
     relationship = new Relationship contents.relationship
     CBucket.addActivities relationship, origin, actor, (err)->
-      console.log 'There was an error adding bucket activities'
+      # console.log 'There was an error adding bucket activities'
       if receiver instanceof JAccount
         username = receiver.getAt('profile.nickname')
         JUser.someData {username}, {email: 1}, (err, cursor)->