Update callback test for new params

Update callback test for new params

--- test/passport/callbacks.coffee
+++ test/passport/callbacks.coffee
@@ -44,14 +44,14 @@
     @request.end.args[0][0](null, res)

   it 'gets a user with an access token apple', (done) ->
-    cbs.apple @req, 'foo-token', 'refresh-token', 'id-token', {}, (err, user) ->
+    cbs.apple @req, 'foo-token', 'refresh-token', { id: 'id-token' }, (err, user) ->
       user.get('accessToken').should.equal 'access-token'
       done()
     @request.post.args[0][0].should
       .equal 'http://apiz.artsy.net/oauth2/access_token'
     queryParams = @request.query.args[0][0]
     queryParams.oauth_provider.should.equal 'apple'
-    queryParams.oauth_token.should.equal 'foo-token'
+    queryParams.oauth_token.should.equal 'id-token'
     queryParams.id_token.should.equal 'id-token'
     res = { body: { access_token: 'access-token' }, status: 200 }
     @request.end.args[0][0](null, res)


Remove unused url module

Remove unused url module

--- lib/layervault/services/upload.coffee
+++ lib/layervault/services/upload.coffee
@@ -1,7 +1,6 @@
-crypto = require 'crypto'
-fs = require 'fs'
-needle = require 'needle'
-url = require 'url'
+crypto  = require 'crypto'
+fs      = require 'fs'
+needle  = require 'needle'

 module.exports = class UploadService
   S3_ENDPOINT = 'https://omnivore-scratch.s3.amazonaws.com'
@@ -39,5 +38,6 @@
         location = resp.headers.location
         location += "&access_token=#{@api.config.accessToken}"

-        needle.post location, {headers: {'Authorization': @api.config.accessToken}}, (err, resp, body) =>
-          cb(err, body)+        needle.post location, {
+          headers: { 'Authorization': @api.config.accessToken }
+        }, (err, resp, body) => cb(err, body)


添加构建倒计时

添加构建倒计时

--- lib/project/build-project-view.coffee
+++ lib/project/build-project-view.coffee
@@ -269,7 +269,6 @@
 			modules = jsonContent['modules']
 			@buildTips.html("正在检测模块信息......")
 			projectPath = pathM.join this.find('select').val(), 'modules'
-			# @checkModuleNeedUpload identifier, version, projectPath for identifier, version of modules
 			moduleList = []
 			@buildTips.html("构建准备......")
 			getModuleMessage = (identifier,version) =>
@@ -278,68 +277,7 @@
 					version: version
 				moduleList.push module
 			getModuleMessage identifier,version for identifier, version of modules
-			# console.log moduleList
 			@checkModuleNeedUpload projectPath, moduleList, 0
-			# @buildTips.html("正在上传应用信息......")
-			# 上传应用信息 获取上传的平台信息
-			# checkboxList = this.find('input[type=checkbox]:checked')
-			# platformInfo = []
-			# iosObj = null
-			# androidObj = null
-			# postAppBuildMessage = (checkbox) =>
-			# 	if $(checkbox).attr('value') is 'iOS'
-			# 		iosObj =
-			# 			logoFileId: ""
-			# 			platform: "IOS"
-			# 			pkgName: @iosName.getText()
-			# 	else
-			# 		androidObj =
-			# 			logoFileId: ""
-			# 			platform: "ANDROID"
-			# 			pkgName: @androidName.getText()
-			# postAppBuildMessage checkbox for checkbox in checkboxList
-			# if iosObj
-			# 	platformInfo.push(iosObj)
-			# if androidObj
-			# 	platformInfo.push(androidObj)
-			# configPath = pathM.join this.find('select').val(),desc.ProjectConfigFileName
-			# console.log configPath
-			# options =
-			# 	encoding: "utf-8"
-			# contentList = JSON.parse(fs.readFileSync(configPath,options))
-			# userMail = Util.store('chameleon').mail
-			# bodyJSON =
-			# 	identifier: @identifier.attr("value"),
-			# 	platformInfo: platformInfo,
-			# 	account: userMail
-			# 	mainModule: contentList["mainModule"]
-			# 	classify: "",
-			# 	version: contentList["version"],
-			# 	describe: contentList["description"],
-			# 	modules: contentList["modules"]
-			# 	releaseNote: contentList["releaseNote"]
-			# bodyStr = JSON.stringify(bodyJSON)
-			# params =
-			# 	body: bodyStr
-			# 	sendCookie: true
-			# 	success: (data) =>
-			# 		# console.log "success "+ JSON.stringify(data)
-			# 		if data["status"] is "success"
-			# 			@buildTips.html("正在排队构建请耐心等待......")
-			# 			# if data["IOS"]
-			# 			#在这里需要不断的查构建结果
-			# 			showObject = (obj) =>
-			# 				if obj.status != 'success'
-			# 					console.error "上传#{obj.platform}不成功！"
-			# 				else
-			# 					console.log "上传#{obj.platform}成功！"
-			# 					@checkBuildResult obj.id,obj.platform,0
-			# 			if data['data'].length > 0
-			# 				showObject obj for obj in data['data']
-			# 	error: =>
-			# 		console.log "error"
-			# client.buildApp(params)
-			# console.log params

 	checkModuleNeedUpload: (modulePath, modules, index) ->
 		if modules.length == 0
@@ -493,24 +431,48 @@

 	checkBuildResult: (id,platform,time) ->
 		# console.log id,platform,time
+		ticket = (timeTips,loopTime,waitTime) =>
+			# console.log timeTips,loopTime
+			if loopTime <= 1
+				return
+			loopTime = loopTime - 1
+			num = waitTime - 1
+			@.find(timeTips).html(num)
+			# console.log timeTips,num,@.find(timeTips).html(num),waitTime
+			setTimeout =>
+				ticket timeTips,loopTime,num
+			,1000
+			# @.find(timeTips).html(num-1)
 		params =
 			sendCookie: true
 			success: (data) =>
 				# data['status'] = "SUCCESS"
 				# data['url'] = "http://baidu.com"
-				console.log data
+				# console.log data
 				if data['code'] == -1
 					alert "#{platform}构建不存在！"
 					return
 				if data['status'] == "WAITING"
 					# setTimeout("checkBuildResult(#{id},#{platform})", 1000*60)
 					if platform == "IOS"
-						@.find(".iosTips").html("构建 IOS 还需等待#{data['waitingTime']}秒")
+						timeTips = ".iosWaitTime"
+						@.find(".iosTips").html("构建 IOS 还需等待#{data['waitingTime']}秒")
 					else
-						@.find(".androidTips").html("构建 ANDOIRD 还需等待#{data['waitingTime']}秒")
+						timeTips = ".androidWaitTime"
+						@.find(".androidTips").html("构建 ANDOIRD 还需等待#{data['waitingTime']}秒")
+					loopTime = 30
+					loopTime2 = 30
+					if data['waitingTime'] < 30
+						loopTime = data['waitingTime']
+						if data['waitingTime'] == 0
+							loopTime = loopTime + 2
+							loopTime2 = data['waitingTime']
 					setTimeout =>
 						@checkBuildResult id,platform,time+1
-					,1000*30
+					,1000*loopTime
+					setTimeout =>
+						ticket timeTips,loopTime2,data['waitingTime']
+					,1000
 				else if data['status'] == "SUCCESS"
 					if !@urlCodeList.is(':visible')
 						@buildingTips.addClass('hide')
@@ -526,98 +488,26 @@
 				else if data['status'] == "BUILDING"
 					@buildTips.html("正在构建请耐心等待......")
 					if platform == "IOS"
-						@.find(".iosTips").html("正在构建 IOS 还需#{data['remainTime']}秒")
+						timeTips = ".iosWaitTime"
+						@.find(".iosTips").html("构建 IOS 还需等待#{data['remainTime']}秒")
 					else
-						@.find(".androidTips").html("正在构建 ANDOIRD 还需#{data['remainTime']}秒")
+						timeTips = ".androidWaitTime"
+						@.find(".androidTips").html("构建 ANDOIRD 还需等待#{data['remainTime']}秒")
+					loopTime = 30
+					if data['remainTime'] < 30
+						loopTime = data['remainTime']
 					setTimeout =>
 						@checkBuildResult id,platform,time+1
-					,1000*30
+					,1000*loopTime
+					setTimeout =>
+						ticket timeTips,loopTime2,data['remainTime']
+					,1000
 				else
 					alert "构建失败"
 					return
 			error: =>
 			 	console.log  "error"
 		client.getBuildUrl(params,id)
-
-
-			# @parentView.nextBtn.attr('disabled',false)
-
-	# checkModuleNeedUpload: ( moduleIdentifer, moduleVersion, modulePath) ->
-	# 	console.log moduleIdentifer, moduleVersion
-	# 	params =
-	# 		sendCookie: true
-	# 		success: (data) =>
-	# 			console.log "check version success"
-	# 			if data['version'] != ""
-	# 				uploadVersion = moduleVersion.split('.')
-	# 				version = data['version'].split('.')
-	# 				# 判断是否需要上传模块
-	# 				if uploadVersion[0] < version[0]
-	# 					console.log "无需更新#{moduleIdentifer} 本地版本为#{moduleVersion},服务器版本为：#{data['version']}"
-	# 					return
-	# 				else if uploadVersion[0] == version[0]
-	# 					if uploadVersion[1] < version[1]
-	# 						console.log "无需更新#{moduleIdentifer} 本地版本为#{moduleVersion},服务器版本为：#{data['version']}"
-	# 						return
-	# 					else if uploadVersion[1] == version[1]
-	# 						if uploadVersion[2] <= version[2]
-	# 							console.log "无需更新#{moduleIdentifer} 本地版本为#{moduleVersion},服务器版本为：#{data['version']}"
-	# 							return
-	# 			# 上传模块
-	# 			# 1、压缩模块
-	# 			# 2、上传
-	# 			console.log "modulePath"
-	# 			# modulePath = pathM.join this.find('select').val(), 'modules', moduleIdentifer
-	# 			if fs.existsSync(modulePath)
-	# 				Util.fileCompression(modulePath)
-	# 				zipPath = modulePath+'.zip'
-	# 				if fs.existsSync(zipPath)
-	# 					console.log zipPath
-	# 					fileParams =
-	# 						formData: {
-	# 							up_file: fs.createReadStream(zipPath)
-	# 						}
-	# 						sendCookie: true
-	# 						success: (data) =>
-	# 							# data = JSON.parse(body)
-	# 							console.log "上传文件成功"
-	# 							if fs.existsSync(pathM.join modulePath,'package.json')
-	# 								packagePath = pathM.join modulePath,'package.json'
-	# 								options =
-	# 									encoding: 'utf-8'
-	# 								contentList = JSON.parse(fs.readFileSync(packagePath,options))
-	# 								console.log contentList['version'],contentList['identifier']
-	# 								params =
-	# 									form:{
-	# 										module_tag: contentList['identifier'],
-	# 										module_name: contentList['name'],
-	# 										module_desc: contentList['description'],
-	# 										version: contentList['version'],
-	# 										url_id: data['url_id'],
-	# 										update_log: "构建应用时发现本地版本高于服务器版本，所以上传 #{contentList['identifier']} 模块"
-	# 									}
-	# 									sendCookie: true
-	# 									success: (data) =>
-	# 										# console.log data
-	# 										# alert "上传模块成功"
-	# 										console.log "upload success"
-	# 									error: =>
-	# 									  alert "error"
-	# 								client.postModuleMessage(params)
-	# 							else
-	# 								console.log "文件不存在#{pathM.join modulePath,'package.json'}"
-	# 						error: =>
-	# 							alert "上传文件失败"
-	# 					client.uploadFile(fileParams,"module","")
-	# 				else
-	# 					alert "打包#{modulePath}失败"
-	# 			else
-	# 				alert "不存在#{modulePath}"
-	# 			# else
-	# 			# 	console.log "需要上传#{moduleIdentifer}模块,服务器版本为空，本地版本为#{moduleVersion}"
-	# 		error : =>
-	# 			console.log "获取模板最新版本 的url 调不通"
-	# 	client.getModuleLastVersion(params,moduleIdentifer)

 	prevBtnClick: ->
 		if @main.is(':visible')


fix https://github.com/sagemathinc/cocalc/issues/2021 -- output indentation style problem in jupyter

--- src/smc-webapp/jupyter/cell-output-message.cjsx
+++ src/smc-webapp/jupyter/cell-output-message.cjsx
@@ -119,9 +119,8 @@

     render: ->

-              {# span?  what? -- See https://github.com/sagemathinc/cocalc/issues/1958 }
-                {@props.value}
-
+            {# span?  what? -- See https://github.com/sagemathinc/cocalc/issues/1958 }
+            {@props.value}

 UntrustedJavascript = rclass


fix a pandoc math issue

fix a pandoc math issue

--- lib/markdown-preview-enhanced-view.coffee
+++ lib/markdown-preview-enhanced-view.coffee
@@ -1029,7 +1029,8 @@

     # fix pandoc math issue
     if @usePandocParser and typeof(MathJax) != 'undefined'
-      mathElements = @element.getElementsByClassName 'math'
+      # @element.getElementsByClassName 'math' doesn't work properly
+      mathElements = @element.querySelectorAll('math.inline, math.display')
       for mathElement in mathElements
         displayMode = mathElement.classList.contains('display')
         tagStart = null


Unlike feature is done \o/

Unlike feature is done \o/

--- client/landing/app/Applications/Activity.kdapplication/views/activityactions.coffee
+++ client/landing/app/Applications/Activity.kdapplication/views/activityactions.coffee
@@ -40,7 +40,7 @@
       , activity

     @likeCount.on "countChanged", (count) =>
-      @updateLikeState(yes)
+      @updateLikeState yes

     @likeLink     = new ActivityActionLink
     @loader       = new KDLoaderView size : width : 14
@@ -48,7 +48,10 @@
   updateLikeState:(checkIfILiked = no)->

     activity = @likeCount.getData()
-    return if activity.meta.likes is 0
+
+    if activity.meta.likes is 0
+      @likeLink.updatePartial "Like"
+      return

     activity.fetchLikedByes {},
       limit : if checkIfILiked then activity.meta.likes else 3
@@ -69,13 +72,6 @@
               peopleWhoLiked.push "<strong>" + firstName + " " + lastName + "</strong>"
             else return

-          # switch activity.meta.likes
-          #   when 0 then tooltip = ""
-          #   when 1 then tooltip = "{{> @peopleWhoLiked0}}"
-          #   when 2 then tooltip = "{{> @peopleWhoLiked0}} and {{> @peopleWhoLiked1}}"
-          #   when 3 then tooltip = "{{> @peopleWhoLiked0}}, {{> @peopleWhoLiked1}} and {{> @peopleWhoLiked2}}"
-          #   else        tooltip = "{{> @peopleWhoLiked0}}, {{> @peopleWhoLiked1}}, {{> @peopleWhoLiked2}} and {{activity.meta.likes - 3}} more."
-
           if activity.meta.likes is 1
             tooltip = peopleWhoLiked[0]
           else if activity.meta.likes is 2
@@ -87,8 +83,8 @@

           @likeCount.updateTooltip {title: tooltip }

-          # if checkIfILiked
-          #   @likeLink.updatePartial if likedBefore then "Unlike" else "Like"
+          if checkIfILiked
+            @likeLink.updatePartial if likedBefore then "Unlike" else "Like"

   viewAppended:->

@@ -127,6 +123,8 @@
               new KDNotificationView
                 title     : "You already liked this!"
                 duration  : 1300
+            else
+              @updateLikeState yes

     @commentLink.registerListener
       KDEventTypes  : "Click"


Handle progress output (by ignoring it)

Handle progress output (by ignoring it)

--- src/browser/auto-updater-win32.coffee
+++ src/browser/auto-updater-win32.coffee
@@ -27,12 +27,12 @@
       return

     args = ['--update', @updateUrl]
-    updateJson = ""
+    updateOutput = ""

     ps = new BufferedProcess
       command: updateDotExe,
       args,
-      stdout: (output) -> updateJson += output
+      stdout: (output) -> updateOutput += output
       exit: (exitCode) ->
         unless exitCode is 0
           console.log 'Failed to update: ' + exitCode + ' - ' + updateJson
@@ -41,9 +41,14 @@

         updateInfo = null
         try
-          updateInfo = JSON.parse updateJson
+          # NB: Update.exe spits out the progress as ints until it completes,
+          # then the JSON is the last line of the output. We don't support progress
+          # so just drop the last lines
+          json = updateOutput.split("\n").reverse()[0]
+
+          updateInfo = JSON.parse json
         catch ex
-          console.log "Update JSON isn't valid: " + updateJson
+          console.log "Update output isn't valid: " + updateOutput
           emit 'update-not-available'
           return


Snapshots less often, awaiting a diff compression

--- client/src/app/io.coffee
+++ client/src/app/io.coffee
@@ -692,11 +692,14 @@
   meta:
     visual: false
   factory: (options) ->
+    SNAPSHOT_FRAME_DELAY = 60 # Only record a snapshot every 60 frames
+
     eventQueue = []
     snapshotQueue = []
     timerId = null
     playerId = null
     playerInfo = {} # Current state of player
+    snapshotFrameCounter = 0 ## Number of frames since last snapshot

     configIsValid = -> options.metrics and options.metrics.gameVersionId and options.metrics.host

@@ -735,6 +738,9 @@
     io =
       enterPlaySequence: ->
         if not configIsValid() then return
+
+        # Reset snapshot counter so that it will be sent on the first frame
+        snapshotFrameCounter = SNAPSHOT_FRAME_DELAY

         # Create player
         jqXhr = $.ajax
@@ -785,14 +791,18 @@
               player: playerId
               userTime: userTime

-          # Send input memory and input IO data as snapshots
-          snapshotQueue.push
-            gameVersion: options.metrics.gameVersionId
-            player: playerId
-            userTime: userTime
-            customData:
-              inputIo: additionalData.inputIoData
-              memory: additionalData.memoryData
+          if snapshotFrameCounter++ >= SNAPSHOT_FRAME_DELAY
+            # Reset snapshot counter
+            snapshotFrameCounter = 0
+
+            # Send input memory and input IO data as snapshots
+            snapshotQueue.push
+              gameVersion: options.metrics.gameVersionId
+              player: playerId
+              userTime: userTime
+              customData:
+                inputIo: additionalData.inputIoData
+                memory: additionalData.memoryData

           # Update player info
           if not _.isEqual(ioData[circuitId].player, playerInfo)


added a test to make sure define dependencies can be overridden

--- src/test/modules-test.coffee
+++ src/test/modules-test.coffee
@@ -101,3 +101,14 @@
     require ['pleaseResetThisModule'], (module) ->
       outcome(testName, module == 'before')
 )()
+
+(->
+  testName = 'modules can be overridden in define dependencies'
+
+  define 'standsTall', -> 'tall'
+  define 'leansOn', ['standsTall'], (tall) -> 'leans on ' + tall
+  define.override 'standsTall', 'pretty high'
+
+  require ['leansOn'], (leans) -> outcome(testName, leans == 'leans on pretty high')
+)()
+


Include area code 376 for Argentina

Include area code 376 for Argentina


Add some doc.

Add some doc.

--- src/coffee/sync/inventory-sync.coffee
+++ src/coffee/sync/inventory-sync.coffee
@@ -4,7 +4,8 @@
 helper = require("../helper")

 ###
-Invetory Sync class
+Inventory Sync class.
+  Ensures that quantityOnStock and expectedDelivery is in sync.
 ###
 class InventorySync extends Sync
   constructor: (opts = {})->


Simplify regexp escaping in text-mate-grammar.coffee

--- src/text-mate-grammar.coffee
+++ src/text-mate-grammar.coffee
@@ -6,7 +6,7 @@
 {Emitter} = require 'emissary'
 {ScopeSelector} = require 'first-mate'

-pathSplitRegex = new RegExp("[#{"\\"+path.sep}.]")
+pathSplitRegex = new RegExp("[#{_.escapeRegExp(path.sep)}.]")

 ### Internal ###


added capability to get a list of versions

--- csi.coffee
+++ csi.coffee
@@ -488,6 +488,25 @@
             log "removing directory #{installedTo}"
             wrench.rmdirSyncRecursive installedTo

+  status:
+    description: """
+    get status parameters for the current package such as the version of the
+    csi packages
+    """
+    action: () ->
+      components = allComponents()
+      if argv.listversions
+        packages = {}
+        t.dfs components, (component)->
+          if component.json.csi and not packages[component.json.csi.name]
+            packages[component.json.csi.name] =
+              version: component.json.version,
+              repo: component.json._from?.split("@")[1..].join("@")
+                .replace(/^git\+ssh:\/\//, ''),
+
+        for pkg, info of packages
+          console.log pkg,info.version,(info.repo || '')
+
 log = (msg, level="info") ->
   console.log "[#{basename process.argv[1]} #{argv._[0]}] #{msg}"

@@ -581,6 +600,12 @@
       "default": false
       describe: "minify javascript in optimized builds\n(cmd: build)"

+    .option "listversions",
+      boolean: true
+      alias: "L"
+      "default": false
+      describe: "list all csi component versions\n(cmd: status)"
+
     .alias("h", "help")

     .wrap(80)


Add 'approve' action to JAccount.can

Add 'approve' action to JAccount.can

--- server/app/bongo/models/account.coffee
+++ server/app/bongo/models/account.coffee
@@ -258,7 +258,7 @@

   can:(action, target)->
     switch action
-      when 'delete','flag'
+      when 'delete','flag','approve'
         @profile.nickname in dummyAdmins or target.originId?.equals @getId()

   fetchRole: secure ({connection}, callback)->


Number Input: Fixed a small visual bug where it was possible to add an extra decimal point. See #55708

--- src/elements/Input/NumberInput.coffee
+++ src/elements/Input/NumberInput.coffee
@@ -160,7 +160,7 @@
 		nn.join("")

 	correctValueForInput: (value) ->
-		value.replace(/[,\.]/, @_decimalpoint)
+		return value.replace(/[,\.]/g, @_decimalpoint)

 	getValueForInput: ->
 		@formatValueForDisplay(null, true)


test for usage arguments in create

test for usage arguments in create

--- spec/create.spec.coffee
+++ spec/create.spec.coffee
@@ -1,6 +1,16 @@
 TestHelper = require "./test_helper"

 describe 'create', ->
+
+  describe "arguments", ->
+
+    it 'gives usage when no directory is specified', =>
+      session = TestHelper.run
+        args: ["create"]
+
+      runs =>
+        expect( session.code ).toBe 1
+        expect( session.stdout ).toMatch("Usage: steroids create <directoryName>")

   describe "new app", ->


Don't show dev tools by default during specs. They steal the focus.

--- spec/spec-helper.coffee
+++ spec/spec-helper.coffee
@@ -11,7 +11,6 @@
 TextMateTheme = require 'text-mate-theme'
 fs = require 'fs'
 require 'window'
-atom.showDevTools()

 requireStylesheet "jasmine.css"


ComputeProvider: fix cyclomatic complexity lint issue

--- workers/social/lib/social/models/computeproviders/computeprovider.coffee
+++ workers/social/lib/social/models/computeproviders/computeprovider.coffee
@@ -1,6 +1,3 @@
-# coffeelint: disable=cyclomatic_complexity
-# FIXME ~GG ^^
-
 { Base, secure, signature } = require 'bongo'

 KONFIG       = require 'koding-config-manager'
@@ -207,6 +204,25 @@
     provider.remove client, options, callback

+  addGroupAdmin = (group, machineInfo, callback) ->
+
+    # TODO Do we need all admins or only some of them? ~ GG
+    # Maybe some of them as admin some of them as user etc.
+    group.fetchAdmin (err, admin) ->
+
+      if not err and admin and not admin.getId().equals account.getId()
+        admin.fetchUser (err, adminUser) ->
+          if not err and adminUser
+            machineInfo.users = [{
+              id       : adminUser.getId(),
+              username : adminUser.username,
+              sudo     : yes, owner: yes
+            }]
+          callback machineInfo
+      else
+        callback machineInfo
+
+
   @generateStackFromTemplate = (data, options, callback) ->

     { account, user, group, template, client } = data
@@ -280,25 +296,10 @@

           # This is optional, since for koding group for example
           # we don't want to add our admins into users machines ~ GG
-          unless options.addGroupAdminToMachines
-            return create machineInfo
-
-          # TODO Do we need all admins or only some of them? ~ GG
-          # Maybe some of them as admin some of them as user etc.
-          group.fetchAdmin (err, admin) ->
-
-            if not err and admin and not admin.getId().equals account.getId()
-              admin.fetchUser (err, adminUser) ->
-                if not err and adminUser
-                  machineInfo.users = [{
-                    id       : adminUser.getId(),
-                    username : adminUser.username,
-                    sudo     : yes, owner: yes
-                  }]
-                create machineInfo
-            else
-              create machineInfo
-
+          if options.addGroupAdminToMachines
+            addGroupAdmin group, machineInfo, (_machine) -> create _machine
+          else
+            create machineInfo

       async.series queue, -> callback null, { stack, results }


ComputeProvider: use MAX_INT

ComputeProvider: use MAX_INT

--- workers/social/lib/social/models/computeproviders/computeprovider.coffee
+++ workers/social/lib/social/models/computeproviders/computeprovider.coffee
@@ -7,6 +7,7 @@
 teamutils   = require './teamutils'
 konstraints = require 'konstraints'

+MAX_INT     = Math.pow(2, 32) - 1

 module.exports = class ComputeProvider extends Base

@@ -395,11 +396,10 @@

     plan = group.getAt 'config.plan'

+    maxAllowed   = MAX_INT
     if plan
       plan       = teamutils.getPlanData plan
       maxAllowed = plan.member
-    else
-      maxAllowed = 9999

     JCounter = require '../counter'
     JCounter[change]
@@ -420,11 +420,10 @@
     plan = group.getAt 'config.plan'
     return callback null  if amount is 0

+    maxAllowed   = MAX_INT
     if plan
       plan       = teamutils.getPlanData plan
       maxAllowed = plan.maxInstance
-    else
-      maxAllowed = 9999

     JCounter = require '../counter'
     JCounter[change]


ComponentLoader: Fix compatibility with CoffeeScript >1.7.0

The Node.JS .coffee handler now needs to be registered explicitly.

--- src/lib/nodejs/ComponentLoader.coffee
+++ src/lib/nodejs/ComponentLoader.coffee
@@ -13,7 +13,10 @@
 internalSocket = require '../InternalSocket'

 # We allow components to be un-compiled CoffeeScript
-require 'coffee-script'
+CoffeeScript = require 'coffee-script'
+if typeof CoffeeScript.register != 'undefined'
+    CoffeeScript.register()
+

 # Disable NPM logging in normal NoFlo operation
 log = require 'npmlog'


Assign a unique id to new paths created.

--- source/javascripts/views/canvas.coffee
+++ source/javascripts/views/canvas.coffee
@@ -18,6 +18,7 @@
       if info.target.type == "path"
         info.target.hasBorders = false
         info.target.hasControls = false
+        info.target.unique_id = _.uniqueId()
         @objects.push info.target

     @canvas.on "mouse:down", (info) =>


Don't assume that atom.packages.loadPackage requires the main module

--- spec/autocomplete-snippets-spec.coffee
+++ spec/autocomplete-snippets-spec.coffee
@@ -1,61 +1,44 @@
 describe 'AutocompleteSnippets', ->
-  [completionDelay, editor, editorView, snippetsMain, autocompleteMain, autocompleteManager] = []
+  [completionDelay, editor, editorView] = []

   beforeEach ->
-    runs ->
-      # Set to live completion
-      atom.config.set('autocomplete-plus.enableAutoActivation', true)
-      # Set the completion delay
-      completionDelay = 100
-      atom.config.set('autocomplete-plus.autoActivationDelay', completionDelay)
-      completionDelay += 100 # Rendering delay
-      workspaceElement = atom.views.getView(atom.workspace)
-      jasmine.attachToDOM(workspaceElement)
+    atom.config.set('autocomplete-plus.enableAutoActivation', true)
+    completionDelay = 100
+    atom.config.set('autocomplete-plus.autoActivationDelay', completionDelay)
+    completionDelay += 100 # Rendering delay

-      autocompleteMain = atom.packages.loadPackage('autocomplete-plus').mainModule
-      spyOn(autocompleteMain, 'consumeProvider').andCallThrough()
-      snippetsMain = atom.packages.loadPackage('autocomplete-snippets').mainModule
-      spyOn(snippetsMain, 'provide').andCallThrough()
+    workspaceElement = atom.views.getView(atom.workspace)
+    jasmine.attachToDOM(workspaceElement)

-    waitsForPromise ->
-      atom.workspace.open('sample.js').then (e) ->
-        editor = e
-        editorView = atom.views.getView(editor)
+    snippetsMainModule = null
+    autocompleteManager = null

     waitsForPromise ->
       Promise.all [
+        atom.workspace.open('sample.js').then (e) ->
+          editor = e
+          editorView = atom.views.getView(editor)
+
         atom.packages.activatePackage('language-javascript')
-        atom.packages.activatePackage('autocomplete-plus')
+
         atom.packages.activatePackage('autocomplete-snippets')
+
+        atom.packages.activatePackage('autocomplete-plus').then (pack) ->
+          autocompleteManager = pack.mainModule.getAutocompleteManager()
+
+        atom.packages.activatePackage('snippets').then ({mainModule}) ->
+          snippetsMainModule = mainModule
+          snippetsMainModule.loaded = false
       ]

-    waitsFor ->
-      autocompleteMain.autocompleteManager?.ready and
-        snippetsMain.provide.calls.length is 1 and
-        autocompleteMain.consumeProvider.calls.length is 1
-
-    runs ->
-      autocompleteManager = autocompleteMain.autocompleteManager
-      spyOn(autocompleteManager, 'findSuggestions').andCallThrough()
-      spyOn(autocompleteManager, 'displaySuggestions').andCallThrough()
-
-  activateSnippetsPackage = ->
-    module = null
-    runs ->
-      module = null
-
-    waitsForPromise ->
-      atom.packages.activatePackage('snippets').then ({mainModule}) ->
-        module = mainModule
-        module.loaded = false
+    waitsFor 'snippets provider to be registered', 1000, ->
+      autocompleteManager?.providerManager.providers.length > 0

     waitsFor 'all snippets to load', 3000, ->
-      module.loaded
+      snippetsMainModule.loaded

   describe 'when autocomplete-plus is enabled', ->
     it 'shows autocompletions when there are snippets available', ->
-      activateSnippetsPackage()
-
       runs ->
         expect(editorView.querySelector('.autocomplete-plus')).not.toExist()

@@ -65,17 +48,14 @@

         advanceClock(completionDelay)

-      waitsFor ->
-        autocompleteManager.displaySuggestions.calls.length is 1
+      waitsFor 'autocomplete view to appear', 1000, ->
+        editorView.querySelector('.autocomplete-plus span.word')

       runs ->
-        expect(editorView.querySelector('.autocomplete-plus')).toExist()
         expect(editorView.querySelector('.autocomplete-plus span.word')).toHaveText('do')
         expect(editorView.querySelector('.autocomplete-plus span.right-label')).toHaveText('do')

     it "expands the snippet on confirm", ->
-      activateSnippetsPackage()
-
       runs ->
         expect(editorView.querySelector('.autocomplete-plus')).not.toExist()

@@ -85,11 +65,8 @@

         advanceClock(completionDelay)

-      waitsFor ->
-        autocompleteManager.displaySuggestions.calls.length is 1
-
-      runs ->
-        expect(editorView.querySelector('.autocomplete-plus')).toExist()
+      waitsFor 'autocomplete view to appear', 1000, ->
+        editorView.querySelector('.autocomplete-plus')

       runs ->
         atom.commands.dispatch(editorView, 'autocomplete-plus:confirm')


exposed the bug with a test, now fix it...

--- spec/coffeescripts/filters_spec.coffee
+++ spec/coffeescripts/filters_spec.coffee
@@ -17,7 +17,7 @@
     expect(hiddenNodeData.nodeIds.length).toEqual(3)
     expect(hiddenNodeData.relIds.length).toEqual(4) #4: rels hidden by user should be included

-  it 'can build hiddenNodeData for 6 node mesh', ->
+  it 'can build hiddenNodeData for 6 node mesh with one hidden rel', ->
         nodeData = [{"id":0},{"id":1},{"id":2},{"id":3},{"id":4},{"id":5}]
         relData = [{"id":0, "start_node":0, "end_node":1, "data":{"rel_type":""}},
                    {"id":1, "start_node":1, "end_node":2, "data":{"rel_type":""}},
@@ -29,9 +29,9 @@
         graph = new Graph(nodeData, relData)

         activatedNode = graph.load(0)
-        relsHiddenByUser = graph.relationships[0..0]
+        relsHiddenByUser = graph.relationships[6..6]
+        console.dir relsHiddenByUser
         hiddenNodeData =  test_buildHiddenNodeData(graph, activatedNode, relsHiddenByUser)

         expect(hiddenNodeData.nodeIds.length).toEqual(0)
-        expect(hiddenNodeData.relIds.length).toEqual(1)
-
+        expect(hiddenNodeData.relIds.length).toEqual(1)


Improve tests. Chai magically can do  and  in the same test, weird.

--- spec/Component.coffee
+++ spec/Component.coffee
@@ -812,7 +812,8 @@
           done()

       sout1.on 'ip', (ip) ->
-        chai.expect(ip).to.eql 'some data'
+        chai.expect(ip).to.be.an 'object'
+        chai.expect(ip.data).to.equal 'some data'
         done()

       c.outPorts.error.attach sout1
@@ -836,7 +837,8 @@
           done()

       sout1.on 'ip', (ip) ->
-        chai.expect(ip).to.eql some: 'data'
+        chai.expect(ip).to.be.an 'object'
+        chai.expect(ip.data).to.equal 'some data'
         done()

       c.outPorts.error.attach sout1


style changes

style changes

--- client/Social/Members/ContentDisplays/userbadgeview.coffee
+++ client/Social/Members/ContentDisplays/userbadgeview.coffee
@@ -19,11 +19,13 @@
     @addSubView @badgeIcon
     @addSubView @title

-class UserPropertyList extends JView
+class UserPropertyList extends KDListView
   constructor:(options = {}, data)->
     options.type = "user-properties"
     # ONLY ADMINS CAN SEE THAT VIEW
     super options, data
+
+  viewAppended:JView::viewAppended
   pistachio:->
     """

User Properties (staff only)


virtualization controller: remove stateful "dialogIsOpen" flag

This is handled in a better way by "Form Workflow"

--- client/landing/app/MainApp/VirtualizationController.coffee
+++ client/landing/app/MainApp/VirtualizationController.coffee
@@ -4,7 +4,6 @@
     super

     @kc = KD.getSingleton("kiteController")
-    @dialogIsOpen = no
     @resetVMData()

     KD.getSingleton('mainController')
@@ -340,27 +339,7 @@
             modal.destroy()

   createPaidVM:->
-    return  if @dialogIsOpen
-
-    # vmController        = KD.getSingleton('vmController')
-    # paymentController   = KD.getSingleton('paymentController')
-
-    # vmController.fetchVMPlans (err, plans) =>
-    #   { descriptions, hostTypes } = vmController.sanitizeVMPlansForInputs plans
-    #   descPartial = ""
-    #   for d in descriptions
-    #     descPartial += """
-    #

-    #

-    #           Good for:
-    #           #{d.meta?.goodFor}
-    #           users
-    #

-    #         #{d.description}
-    #

-    #       """
-
-    #   paymentInput = {}
+
     productForm = new VmProductForm

     paymentController = KD.getSingleton 'paymentController'
@@ -368,7 +347,8 @@
     KD.whoami().fetchPlansAndSubscriptions ['vm'], (err, plansAndSubscriptions) =>
       return  if KD.showError err

-      { subscriptions } = paymentController.groupPlansBySubscription plansAndSubscriptions
+      { subscriptions } = paymentController
+                            .groupPlansBySubscription plansAndSubscriptions

       productForm.setCurrentSubscriptions subscriptions

@@ -377,22 +357,22 @@
       KD.getGroup().fetchProducts 'pack', options, (err, packs) ->
         return  if KD.showError err

-        productForm.setContents packs
+        productForm.setContents 'packs', packs

     workflow = new PaymentWorkflow
       productForm : productForm
       confirmForm : new VmPaymentConfirmForm

-    modal = new BuyModal
-      title       : "Create a new VM"
-      workflow    : workflow
+    modal = new KDModalView
+      title   : "Create a new VM"
+      view    : workflow
+      height  : "auto"
+      width   : 500
+      overlay : yes
+
+    workflow.on 'DataCollected', -> debugger

     workflow.enter()
-
-    workflow.on 'DataCollected', -> debugger
-
-    @dialogIsOpen = yes
-    modal.once 'KDModalViewDestroyed', => @dialogIsOpen = no

   showPaymentMethodForm: (modal) ->

@@ -442,8 +422,6 @@

       else
         return callback yes
-
-    return  if @dialogIsOpen

     modal = new KDModalView
       title          : "Approval required"
@@ -464,18 +442,10 @@
             modal.destroy()
             callback no

-    modal.once 'KDModalViewDestroyed', =>
-      @dialogIsOpen = no
-      callback no
-
-    @dialogIsOpen = yes
-
   askToTurnOn:(options, callback)->

     [options, callback] = [callback, options]  if typeof options is "function"
     {appName, vmName, state} = options
-
-    return  if @dialogIsOpen

     title   = "Your VM is turned off"
     content = """To #{if appName then 'run' else 'do this'} #{appName}
@@ -543,9 +513,6 @@

     modal.once 'KDModalViewDestroyed', -> callback? destroy: yes

-    @dialogIsOpen = yes
-    modal.once 'KDModalViewDestroyed', => @dialogIsOpen = no
-
   # there may be a better place for these who methods below - SY

   fetchVMPlans: (callback) ->


Don't show expensed VM button, if user has no quota.

--- client/landing/app/MainApp/VirtualizationController.coffee
+++ client/landing/app/MainApp/VirtualizationController.coffee
@@ -108,7 +108,6 @@
     vmController        = @getSingleton('vmController')
     canCreateSharedVM   = "owner" in KD.config.roles or "admin" in KD.config.roles
     canCreatePersonalVM = "member" in KD.config.roles
-    canCreateExpensedVM = yes

     # Take this to a better place, possibly to payment controller.
     makePayment = (type, plan, callback)->
@@ -193,11 +192,6 @@
     else
       return new KDNotificationView
         title : "You are not authorized to create VMs in #{group} group"
-
-    if canCreateExpensedVM
-      content += """

-                    You can also get a Personal VM using your
-                    goups quota. Your group will be charged."""

     vmController.fetchVMPlans (err, plans)=>
       @paymentPlans = plans
@@ -299,8 +293,13 @@
       if canCreateSharedVM
         modal.modalTabs.forms["Create VM"].buttons.group.show()

-      if canCreateExpensedVM
-        modal.modalTabs.forms["Create VM"].buttons.expensed.show()
+      group = KD.singletons.groupsController.getCurrentGroup()
+      group.vmUsage (err, limit)->
+        if limit and limit.usage < limit.quota
+          # content += """

+          #               You can also get a Personal VM using your
+          #               goups quota. Your group will be charged."""
+          modal.modalTabs.forms["Create VM"].buttons.expensed.show()

       @dialogIsOpen = yes
       modal.once 'KDModalViewDestroyed', => @dialogIsOpen = no


minor test refactor

minor test refactor

--- test/mongo-watcher.coffee
+++ test/mongo-watcher.coffee
@@ -97,14 +97,13 @@
       @watcher.watch 'test.users', (event) ->
         should.exist event.timestamp
         should.exist event.targetId
+        expected = {
+          operation: 'set'
+          path: '.'
+          data: {email: 'graham@daventry.com'}
+        }
         for op in event.oplist
-          expected = {
-            operation: 'set'
-            path: '.'
-            data: {email: 'graham@daventry.com'}
-          }
-          if isEqual op, expected
-            done()
+          done() if isEqual op, expected

       @users.insert {email: 'graham@daventry.com'}, (err, status) ->
         should.not.exist err


Skip the net.connect test on Windows.

Skip the net.connect test on Windows.

--- spec/node-spec.coffee
+++ spec/node-spec.coffee
@@ -77,11 +77,9 @@

   describe 'net.connect', ->
     it 'emit error when connect to a socket path without listeners', (done) ->
-      socketPath =
-        if process.platform is 'win32'
-          '\\\\.\\pipe\\atom-shell-test-sock'
-        else
-          path.join os.tmpdir(), 'atom-shell-test.sock'
+      return done() if process.platform is 'win32'
+
+      socketPath = path.join os.tmpdir(), 'atom-shell-test.sock'
       script = path.join(fixtures, 'module', 'create_socket.js')
       child = child_process.fork script, [socketPath]
       child.on 'exit', ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/color.coffee
+++ test/color.coffee
@@ -208,7 +208,7 @@
   equalEnough hsl[3], 1.0

 test "#hue", ->
-  color = Color(34, 54, 239).hue(20)
+  color = Color(34, 54, 239).shiftHue(20)

   equalEnough color.r, 84
   equalEnough color.g, 37
@@ -218,7 +218,7 @@
 test "#hue$", ->
   color = Color(34, 54, 239)

-  color.hue$(60)
+  color.shiftHue$(60)

   equalEnough color.r, 218
   equalEnough color.g, 37


Fix my js_loader patch

Fix my js_loader patch

* still call the callback when skipping duplicate file.  This updates the completed flags, and so the overall "we're done" callback fires properly.

--- common/lib/xmodule/xmodule/js/src/javascript_loader.coffee
+++ common/lib/xmodule/xmodule/js/src/javascript_loader.coffee
@@ -62,5 +62,9 @@
         # Need to use the DOM elements directly or the scripts won't execute
         # properly.
         $('head')[0].appendChild(s)
-        $(placeholder).remove()
+      else
+        # just call the completion callback directly, without reloading the file
+        completionHandlerGenerator(index)()
+      $(placeholder).remove()
+


rm test/index.coffee

--- test/index.coffee
+++ test/index.coffee
@@ -1,196 +0,0 @@
-# !::coffee [.]
-
-Util = require 'util'
-Fs   = require 'fs-plus'
-Path = require 'path'
-
-GI = require '../lib/index'
-GI.startLoop()
-
-Gdk       = GI.importNS 'Gdk', '3.0'
-Gtk       = GI.importNS 'Gtk', '3.0'
-GtkSource = GI.importNS 'GtkSource', '3.0'
-Vte       = GI.importNS 'Vte'
-#Terminal = require './terminal'
-
-Gtk.init null
-
-Orientation  = Gtk.Orientation
-StyleContext = Gtk.StyleContext
-CssProvider  = Gtk.CssProvider
-
-schemeManager = GtkSource.StyleSchemeManager.get_default()
-langManager   = GtkSource.LanguageManager.get_default()
-scheme        = schemeManager.get_scheme("builder-dark")
-
-css = new CssProvider
-css.load_from_path Path.join __dirname, 'style.css'
-
-win = new Gtk.Window
-    title:           'node-gtk'
-    type:            Gtk.WindowType.TOPLEVEL
-    window_position: Gtk.WindowPosition.CENTER
-win.set_default_size 600, 400
-#win.set_decorated false
-#win.set_resizable false
-win.connect 'show',    Gtk.main
-win.connect 'destroy', Gtk.main_quit
-
-#box      = new Gtk.Box Orientation.HORIZONTAL
-grid     = new Gtk.Grid
-
-header = new Gtk.HeaderBar
-label  = new Gtk.Label 'label'
-header.add label
-
-entryView   = new Gtk.Entry
-entryView.set_icon_from_icon_name Gtk.EntryIconPosition.PRIMARY, 'application-exit-symbolic'
-entryView.get_style_context().add_provider css, 9999
-entryView.name = 'entry'
-
-scrollView = new Gtk.ScrolledWindow
-textView   = new GtkSource.View
-scrollView.add textView
-
-btn = new Gtk.Button("yo")
-header.add btn
-
-pop = new Gtk.Popover(btn)
-pop.add new Gtk.Label("yo")
-pop.set_size_request(50, 100)
-
-scrollView.margin  = 10
-
-textView.vexpand                = true
-textView.hexpand                = true
-textView.monospace              = true
-textView.show_line_numbers      = true
-textView.highlight_current_line = true
-textView.get_style_context().add_provider css, 9999
-
-buffer = textView.get_buffer()
-buffer.set_highlight_syntax true
-buffer.set_style_scheme     scheme
-
-#termView   = new Vte.Terminal
-
-#grid.attach             x, y, w, h
-grid.attach header,      0, 0, 2, 1
-grid.attach scrollView,  0, 1, 2, 1
-#grid.attach termView,    1, 1, 1, 1
-grid.attach entryView,   0, 2, 2, 1
-win.add grid
-
-# File loading
-loadFile = (filename) ->
-    try
-        content = Fs.readFileSync(filename)
-        lang = langManager.guess_language filename, null
-        label.set_text filename
-        buffer.set_language lang
-        buffer.set_text(content, -1)
-        buffer.filename = filename
-        textView.grab_focus()
-    catch err
-        buffer.set_language null
-        buffer.set_text(err.toString(), -1)
-
-saveFile = (filename) ->
-    try
-        filename = buffer.filename unless filename?
-
-        start = buffer.get_start_iter()
-        end   = buffer.get_end_iter()
-        content = buffer.get_text(start, end, false)
-
-        #console.log content
-        Fs.writeFileSync(filename, content)
-        console.log "#{filename} written #{content.length}"
-    catch err
-        console.error err
-
-resolvePath = (file) ->
-    Path.resolve(__dirname, Fs.normalize(file))
-
-# Code execution
-safeEval = (code) ->
-    try
-        res = eval(code)
-        return res
-    catch err
-        console.error err
-    return null
-
-# Command execution
-execute = (command) ->
-    tokens = command.split(' ')
-    if tokens[0] == 'e'
-        if tokens.length > 1
-            loadFile(resolvePath(tokens[1]))
-        else if buf.filename?
-            loadFile(buf.filename)
-        else
-         console.log('No filename')
-    else if tokens[0] == 'w'
-        if tokens.length > 1
-            saveFile(resolvePath(tokens[1]))
-        else
-            saveFile()
-    else if tokens[0] == 'q'
-        win.close()
-        process.exit()
-    else if tokens[0] == 'pop'
-        pop.show_all()
-    else
-        console.log command, ' => ', safeEval(command)
-
-# Event handling
-textView.connect 'key-press-event', (widget, event) ->
-    key = event.keyval
-    keyname = Gdk.keyval_name(key)
-    console.log 'KeyPress: ', Gtk.accelerator_get_label(event.keyval, event.state)
-
-    if keyname.match /(semi)?colon/
-        entryView.grab_focus()
-        return true
-
-    if key == Gdk.KEY_G
-        buffer.place_cursor buffer.get_end_iter()
-        return true
-
-    if key == Gdk.KEY_g
-        start = buffer.get_start_iter()
-        buffer.place_cursor start
-        return true
-
-    return false
-
-entryView.history = ['buf.get_start_iter()']
-entryView.connect 'key-press-event', (widget, event) ->
-    keyname = Gdk.keyval_name(event.keyval)
-    console.log 'CmdPress: ', Gtk.accelerator_get_label(event.keyval, event.state)
-    if event.keyval == Gdk.KEY_Tab
-        entryView.set_text(entryView.history[0])
-        return true
-    else if keyname == 'Escape'
-        textView.grab_focus()
-    else if keyname == 'Return'
-        text = entryView.get_text()
-        entryView.set_text('')
-        entryView.history.unshift text
-        execute text
-
-btn.connect 'clicked', () ->
-    if pop.get_visible()
-        pop.hide()
-    else
-        pop.show_all()
-
-
-# UI loop start
-
-buf = buffer
-
-loadFile Path.join(__dirname, 'index.coffee')
-
-win.show_all()


test: generate additonal error tests

test: generate additonal error tests

--- test/index.coffee
+++ test/index.coffee
@@ -147,31 +147,35 @@
       .map((el={}) -> (el[key] for key in _(el).keys().sort()))
       .flatten().value().join ''

+make_configs = (config) ->
+  # If on is a string, also try the hash version of on to verify it's the same
+  if not _(config.on).isString()
+    mod_configs = [_(config).deepClone()]
+  else
+    mod_configs = [_(config).deepClone(), _(config).deepClone()]
+    mod_configs[1].on = _.object config.on, config.on
+  return mod_configs if config.type
+  # If type isn't specified, assume that the expected values are for type=full and create tests
+  # for the other types by filtering it
+  mod_configs = _(mod_configs).chain()
+    .map (mod_config) ->
+      # If we don't specify type, generate an expected array for each type based on the original
+      type_spec =
+        left: (expected_pair) -> expected_pair[0]?
+        right: (expected_pair) -> expected_pair[1]?
+        inner: (expected_pair) -> expected_pair[0]? and expected_pair[1]?
+        full: -> true
+      _(type_spec).map (filter, type) ->
+        new_config = _(mod_config).deepClone()
+        _(new_config).extend {type},
+          if new_config.expected then {expected: _(new_config.expected).filter filter} else {}
+    .flatten()
+    .value()
+
 describe 'joins', ->
   _(configs).each (config, i) ->
     return if config.error
-    # If on is a string, also try the hash version of on to verify it's the same
-    if _(config.on).isString()
-      mod_configs = [_(config).deepClone(), _(config).deepClone()]
-      mod_configs[1].on = _.object config.on, config.on
-    else
-      mod_configs = [_(config).deepClone()]
-    # If type isn't specified, assume that the expected values are for type=full and create tests
-    # for the other types by filtering it
-    unless config.type
-      mod_configs = _(mod_configs).chain()
-        .map (mod_config) ->
-          # If we don't specify type, generate an expected array for each type based on the original
-          type_spec =
-            left: (expected_pair) -> expected_pair[0]?
-            right: (expected_pair) -> expected_pair[1]?
-            inner: (expected_pair) -> expected_pair[0]? and expected_pair[1]?
-            full: -> true
-          _(type_spec).map (filter, type) ->
-            new_config = _(mod_config).deepClone()
-            _(new_config).extend {type}, expected: _(new_config.expected).filter filter
-        .flatten()
-        .value()
+    mod_configs = make_configs config
     _(mod_configs).each (mod_config, j) ->
       test_num = "#{i + 1}.#{j + 1}"
       it "joins config ##{test_num} - #{JSON.stringify mod_config}", (done) ->
@@ -182,9 +186,11 @@
           done()
   _(configs).each (config, i) ->
     return unless config.error
-    test_num = "#{i + 1}.1"
-    it "fails on config ##{test_num} - #{JSON.stringify config}", (done) ->
-      [left, right] = _([config.left, config.right]).map (arr) -> _(arr).stream().stream()
-      _(join left, right, {on: config.on, type: config.type}).stream().run (err, results) ->
-        assert.equal err?.message, config.error
-        done()
+    mod_configs = make_configs config
+    _(mod_configs).each (mod_config, j) ->
+      test_num = "#{i + 1}.#{j + 1}"
+      it "fails on config ##{test_num} - #{JSON.stringify config}", (done) ->
+        [left, right] = _([config.left, config.right]).map (arr) -> _(arr).stream().stream()
+        _(join left, right, {on: config.on, type: config.type}).stream().run (err, results) ->
+          assert.equal err?.message, config.error
+          done()


Work around visionmedia/mocha#1241

Work around visionmedia/mocha#1241

--- test/index.coffee
+++ test/index.coffee
@@ -10,7 +10,13 @@

   before ->
     eq = (input, expected) ->
-      expect(index input).to.equal expected  # just compares strings, no deep equal needed
+      actual = index input
+      try
+        expect(actual).to.equal expected  # just compares strings, no deep equal needed
+      catch err
+        err.message = 'not equal'
+        err.showDiff = false
+        throw err

   it 'should accept empty input', ->
     eq '', '''


tests for globals

tests for globals

--- test/index.coffee
+++ test/index.coffee
@@ -5,6 +5,15 @@
 describe "OrderedMap()", ->
   it "is a function", ->
     assert.equal typeof OrderedMap, "function"
+
+  it "can could be called with `new` I guess", ->
+    ordered_map = new OrderedMap()
+    assert.equal ordered_map instanceof OrderedMap, false
+
+  it "can be called without `new`, and without global side effects, because duh", ->
+    num_globals = Object.keys(global).length
+    ordered_map = OrderedMap()
+    assert.equal num_globals, Object.keys(global).length

 describe "Methods", ->
   instance = undefined


simplify index test case

simplify index test case

--- test/index.coffee
+++ test/index.coffee
@@ -4,14 +4,9 @@
   # shared
   # ----------------------------------------------------------

-  root    = 'globDir'
-  glob    = p.join root, '*'
-  dir     = p.join root, 'dir'
-  file    = p.join root, 'file'
-  exe     = p.join root, 'exe'
-  symlink = p.join root, 'symlink'
-  abs     = p.join process.cwd(), file
-  target  = 'file'
+  root = 'globDir'
+  glob = p.join root, '*'
+  file = p.join root, 'file'

   statBase =
     isDirectory: -> false
@@ -19,44 +14,15 @@
     isFile: -> false
   stat = (ob) -> Object.assign {}, statBase, ob

-  typeMap = new Map [
-    [dir, stat isDirectory: -> true]
-    [symlink, stat isSymbolicLink: -> true]
-  ]
-  [file, exe, abs].forEach (type) ->
-    typeMap.set type, stat isFile: -> true
-
-  want =
-    glob: glob
-    root: root + sep
-    contents:
-      file: {}
-      dir: {}
-      exe: {}
-      symlink: {}
-  want.contents.file[file] = true
-  want.contents.dir[dir] = true
-  want.contents.exe[exe] = true
-  want.contents.symlink[symlink] =
-    target:
-      type: 'file'
-      path: target
-
   # ----------------------------------------------------------
   # hooks
   # ----------------------------------------------------------

   before ->
-
     sinon.stub proms
       , 'getStats'
-      , (path) -> Promise.resolve typeMap.get path
-
-    mock globDir:
-      dir: mock.directory()
-      file: mock.file()
-      exe: mock.file mode: 755
-      symlink: mock.symlink path: target
+      , -> Promise.resolve stat isFile: -> true
+    mock globDir: file: mock.file()

   after ->
     mock.restore()
@@ -69,4 +35,11 @@
   it 'return {glob: <String>, root: <String>, contents: <Object>}', ->
     t.deepEqual
       have: yield globStats glob
-      want: want
+      want:
+        glob: glob
+        root: root + sep
+        contents:
+          file: "#{file}": true
+          dir: {}
+          exe: {}
+          symlink: {}


FAIL intentionally - run 'gulp' and fix

FAIL intentionally - run 'gulp' and fix

--- test/index.coffee
+++ test/index.coffee
@@ -2,4 +2,4 @@
 lib = require '../'

 it 'should return 123', ->
-  lib().should.equal 123
+  lib().should.equal 123x


Update try/catch tests for the new style

Signed-off-by: Ian Macalinao <08370e385f4778cad8fe504ec5445edd3d45bd9a@gmail.com>

--- test/items.coffee
+++ test/items.coffee
@@ -27,8 +27,8 @@
     it "should throw an error if the filter isn't a string, object, or function", ->
       try
         find 3.14
+        chai.assert.fail(0, 0, "Invalid value didn't error!")
       catch e
-        e.message.should.equal "Invalid arguments for find!"

     it "should return a blank array if the filter is never true", ->
       find(-> false).should.eql []
@@ -137,8 +137,8 @@
       it "should error if the property is not a number", ->
         try
           find().sort byProperty("name")
+          chai.assert.fail(0, 0, "Invalid value didn't error!")
         catch e
-          e.message.should.equal "The property must be a number."

       it "should sort ascending by default", ->
         res = find(withStats(["ap"])).sort byProperty("ap")


Change DNT Connect endpoint to www.dnt.no for test

--- test/suite.coffee
+++ test/suite.coffee
@@ -123,7 +123,7 @@

     params = gs.parse params

-    assert.equal url, 'https://www.turistforeningen.no/connect/bounce/'
+    assert.equal url, 'https://www.dnt.no/connect/bounce/'
     assert.equal params.client, 'myApp'
     assert.equal typeof params.data, 'string'
     assert.equal typeof params.hmac, 'string'
@@ -140,7 +140,7 @@

     params = gs.parse params

-    assert.equal url, 'https://www.turistforeningen.no/connect/bounce/'
+    assert.equal url, 'https://www.dnt.no/connect/bounce/'
     assert.equal params.client, 'myApp'
     assert.equal typeof params.data, 'string'
     assert.equal typeof params.hmac, 'string'
@@ -151,7 +151,7 @@

     params = gs.parse params

-    assert.equal url, 'https://www.turistforeningen.no/connect/signon/'
+    assert.equal url, 'https://www.dnt.no/connect/signon/'
     assert.equal params.client, 'myApp'
     assert.equal typeof params.data, 'string'
     assert.equal typeof params.hmac, 'string'


oops -- one small fix.

oops -- one small fix.

--- src/smc-webapp/jupyter/actions.coffee
+++ src/smc-webapp/jupyter/actions.coffee
@@ -117,16 +117,16 @@
             @syncdb.on('metadata-change', @sync_read_only)
             @syncdb.on('connected', @sync_read_only)

-        @syncdb.on('change', @_syncdb_change)
-
-        if @_is_project
-            # Wait until the .ipynb file has actually been parsed into
+            # Browser Client: Wait until the .ipynb file has actually been parsed into
             # the (hidden, e.g. .a.ipynb.sage-jupyter2) syncdb file,
             # then set the kernel, if necessary.
             @syncdb.wait
                 until : (s) =>
                     return !!s.get_one({"type":"file"})
                 cb    : => @_syncdb_init_kernel()
+
+
+        @syncdb.on('change', @_syncdb_change)

         @syncdb.once 'change', =>
             # Important -- this also gets run on the backend, where


:art: More expressive specs

:art: More expressive specs

--- spec/text-editor-presenter-spec.coffee
+++ spec/text-editor-presenter-spec.coffee
@@ -8,7 +8,7 @@
 describe "TextEditorPresenter", ->
   # These `describe` and `it` blocks mirror the structure of the ::state object.
   # Please maintain this structure when adding specs for new state fields.
-  describe "::state", ->
+  describe "::getState()", ->
     [buffer, editor] = []

     beforeEach ->
@@ -43,13 +43,22 @@
       for key, value of expected
         expect(actual[key]).toEqual value

-    expectStateUpdate = (presenter, fn) ->
+    expectStateUpdatedToBe = (value, presenter, fn) ->
       updatedState = false
       disposable = presenter.onDidUpdateState ->
         updatedState = true
         disposable.dispose()
       fn()
-      expect(updatedState).toBe true
+      expect(updatedState).toBe(value)
+
+    expectStateUpdate = (presenter, fn) -> expectStateUpdatedToBe(true, presenter, fn)
+
+    expectNoStateUpdate = (presenter, fn) -> expectStateUpdatedToBe(false, presenter, fn)
+
+    describe "during state retrieval", ->
+      it "does not trigger onDidUpdateState events", ->
+        presenter = buildPresenter()
+        expectNoStateUpdate presenter, -> presenter.getState()

     describe ".horizontalScrollbar", ->
       describe ".visible", ->


reject on Error

reject on Error

--- src/client/embeds/strategies/js.coffee
+++ src/client/embeds/strategies/js.coffee
@@ -33,8 +33,9 @@
         'data-hull-ship-script'      : @deployment.ship.id

       scriptLoader({src:@deployment.ship.index, attributes})
-      .then @ready.resolve
-      .catch throwErr
+      .then (args...)=>
+        @ready.resolve(args...)
+      .catch @ready.reject
     else
       new Promise (resolve, reject)=>
         @ready.resolve()


Now correctly update the items list and update total amount depending on the quantity

--- app/assets/javascripts/backend/interventions.js.coffee
+++ app/assets/javascripts/backend/interventions.js.coffee
@@ -260,6 +260,7 @@

     generateItemsArrayFromId: (input)->
       id = input.parent().find('.selector-value').val()
+      $('.purchase-items-array').empty()
       itemHeader = []
       itemHeader.push("Article")
       itemHeader.push("Quantité")
@@ -280,7 +281,10 @@
                 itemLine.push("" + item.amount + "")
                 $('.purchase-items-array').append("
" + itemLine.join('') + "
")

-
+    updateTotalAmount: (input) ->
+      quantity = input.val()
+      totalAmount = quantity * parseFloat(input.parents('li.item-line').find('.item-pretax-amount').html())
+      input.parents('li.item-line').find('.item-amount').html(totalAmount)

   ##############################################################################
   # Triggers
@@ -339,6 +343,10 @@
     $(this).each ->
       E.interventions.generateItemsArrayFromId($(this))

+  $(document).on "keyup change", 'input.input-quantity', ->
+    $(this).each ->
+      E.interventions.updateTotalAmount($(this))
+

   $(document).ready ->


Keep mousedown listener while using rect tool

--- app/assets/javascripts/modules/canvas.module.js.coffee
+++ app/assets/javascripts/modules/canvas.module.js.coffee
@@ -29,7 +29,7 @@
   selectTool: (tool, action) ->
     if action is 'draw' then @drawSnapPoints() else @clearCanvas()

-    if tool is 'rect' then @canvas.addEventListener 'mousedown', @drawRect
+    if tool is 'rect' then @drawRect()

     'move your mouse near a snap point and click to ' + action

@@ -45,52 +45,53 @@
       @context.fill()
       @context.closePath()

-  drawRect: (e) =>
-    # Find the x and y within canvas
-    bounds = @canvas.getBoundingClientRect()
-    x = e.clientX - bounds.left
-    y = e.clientY - bounds.top
+  drawRect: ->
+    @canvas.addEventListener 'mousedown', (e) =>
+      # Find the x and y within canvas
+      bounds = @canvas.getBoundingClientRect()
+      x = e.clientX - bounds.left
+      y = e.clientY - bounds.top

-    # TODO - generalize this to any step
-    writeStep = (start, end) ->
-      $('#rect-1')
-        .html('
Draw a rectangle from ' +
-          start + ' to ' + end + '
')
+      # TODO - generalize this to any step
+      writeStep = (start, end) ->
+        $('#rect-1')
+          .html('
Draw a rectangle from ' +
+            start + ' to ' + end + '
')

-    for point in @snapPoints
-      # Is it near a snap point?
-      withinX = Math.abs(point.x - x) < point.radius
-      withinY = Math.abs(point.y - y) < point.radius
+      for point in @snapPoints
+        # Is it near a snap point?
+        withinX = Math.abs(point.x - x) < point.radius
+        withinY = Math.abs(point.y - y) < point.radius

-      if withinX and withinY
-        # TODO - generalize this to any step
-        $('#controls-steps').append '

'
-        writeStep point.name, point.name
+        if withinX and withinY
+          # TODO - generalize this to any step
+          $('#controls-steps').append '

'
+          writeStep point.name, point.name

-        # Listen for mouse drags to draw rect
-        window.addEventListener 'mousemove', (moveEvent) =>
-          rectWidth = moveEvent.clientX - bounds.left
-          rectHeight = moveEvent.clientY - bounds.top
+          # Listen for mouse drags to draw rect
+          window.addEventListener 'mousemove', (moveEvent) =>
+            rectWidth = moveEvent.clientX - bounds.left
+            rectHeight = moveEvent.clientY - bounds.top

-          @drawSnapPoints()
-          @context.strokeStyle = '#333333'
-          @context.fillStyle = '#5BC0DE'
-          @context.beginPath()
-          @context.rect point.x, point.y,
-                        - (point.x - rectWidth)
-                        - (point.y - rectHeight)
-          @context.stroke()
-          @context.fill()
-          @context.closePath()
+            @drawSnapPoints()
+            @context.strokeStyle = '#333333'
+            @context.fillStyle = '#5BC0DE'
+            @context.beginPath()
+            @context.rect point.x, point.y,
+                          - (point.x - rectWidth)
+                          - (point.y - rectHeight)
+            @context.stroke()
+            @context.fill()
+            @context.closePath()

-          writeStep point.name, rectWidth + ' ' + rectHeight
+            writeStep point.name, rectWidth + ' ' + rectHeight

-          # Save context of mousemove event
-          dragListener = arguments.callee
-          @canvas.addEventListener 'mouseup', (e) =>
-            # Stop listening for drag events
-            window.removeEventListener 'mousemove', dragListener
-        # Found our snap point, can break from for loop
-        break
+            # Save context of mousemove event
+            dragListener = arguments.callee
+            @canvas.addEventListener 'mouseup', (e) =>
+              # Stop listening for drag events
+              window.removeEventListener 'mousemove', dragListener
+          # Found our snap point, can break from for loop
+          break


fix weird resorting of email triggers

fix weird resorting of email triggers

Former-commit-id: 01f5ddda55186c9b3aa6b9e3bbd5c72c4bc44ea4 [formerly 558bd272db443db5e686adc20370e912ef6a7adb] [formerly 128130b4440241d959dc256f364f58e5d1b75c94 [formerly b4cbf4930e15641ab1c022e4ff028ef6e9894fc7]] [formerly e33c61456cd8ffabea30529306839db5a01d4122 [formerly c32e54634f1cde17839caa0373d122ef10c085bb] [formerly b3bf346059a57473859877600450cde03ed45168 [formerly b3bf346059a57473859877600450cde03ed45168 [formerly b00a8c2992d6058960fa62f1641fa1878ad8b664]]]]
Former-commit-id: dc491adbad0ba57da5d0f64d1ed16d761927d479 [formerly 7bcb68c8d61246e9e22c6e9652200ec398511cf3] [formerly b20b337269dc47c664772af7fca9b3cd420e0d18 [formerly 45954c595070ebfa598e26bf4dacb93cf763babb]]
Former-commit-id: 4e37b99cb4ce8e41c085f0aa10490a78b19bb282 [formerly 0fd5bfb7f36ccc48c8d3a86445669edba68caf0d]
Former-commit-id: 83cae6c522f21d88b60213ef563569043d5e702b
Former-commit-id: c4d2f47ca495a87702379709278d0759df771cb1

--- @client/notifications.coffee
+++ @client/notifications.coffee
@@ -301,9 +301,12 @@
                 listStyle: 'none'

-              for event_name, event_relations of relation_config.events
+              for event_name in _.keys(relation_config.events).sort()
+                event_relations = relation_config.events[event_name]
                 idx = 0
-                for event_relation, event_relation_settings of event_relations
+                for event_relation in _.keys(event_relations).sort()
+                  event_relation_settings = event_relations[event_relation]
+
                   idx += 1
                   LI
                     style:


Fix test for low resolution OS timers.

Fix test for low resolution OS timers.

--- test/roomMessaging.coffee
+++ test/roomMessaging.coffee
@@ -483,7 +483,7 @@
                 expect(info2.joined).false
                 expect(info2.timestamp).a('Number')
                 expect(info2.timestamp).within(ts, tsmax)
-                expect(info2.timestamp).above(info1.timestamp)
+                expect(info2.timestamp).least(info1.timestamp)
                 done()

   it 'should get empty seen info for unseen users', (done) ->


set key

set key

--- src/components/tabs-with-children.cjsx
+++ src/components/tabs-with-children.cjsx
@@ -24,7 +24,7 @@

   renderTab: (tab, index) ->
     isSelected = index is @state.tabIndex
-

+

       <a role="tab"
         href="" tabIndex="-1" onClick={_.partial(@onTabClick, index)}
         aria-selected={'true' if isSelected}


rename refreshDashboard to refreshBuildState

--- js/app.coffee
+++ js/app.coffee
@@ -77,9 +77,7 @@
     mixpanel.track("Auth GitHub Modal Why Necessary")
     $(".why_authenticate_github_modal").slideDown()

-  # Project dashboard will eventually be merged into regular dashboard,
-  # and this name will be more correct
-  refreshDashboard: () =>
+  refreshBuildState: () =>
     VM.loadProjects()
     sel = VM.selected()
     if sel.admin_builds
@@ -88,6 +86,10 @@
       VM.loadProject(sel.username, sel.project, sel.branch, true)
     else
       VM.loadRecentBuilds()
+
+  # Keep this until backend has a chance to fully deploy
+  refreshDashboard: () =>
+    @refreshBuildState()

   performDocSearch: (query) =>
     $.ajax


Removing 'leaderboardId' to avoid confusion.

--- src/Carrot.coffee
+++ src/Carrot.coffee
@@ -110,11 +110,12 @@
     @postSignedRequest("/me/achievements.json",
       {'achievement_id': achievementId}, @callbackHandler(callback))

-  postHighScore: (score, leaderboardId, callback) ->
+  postHighScore: (score, callback) ->
     @postSignedRequest("/me/scores.json",
-      {'value': score, 'leaderboard_id': leaderboardId | ""}, @callbackHandler(callback))
+      {'value': score}, @callbackHandler(callback))

   postAction: (actionId, objectInstanceId, actionProperties, objectProperties, callback) ->
+    # TODO: Error checking for required properties
     actionProperties = if typeof actionProperties is "string" then actionProperties else JSON.stringify(actionProperties || {})
     objectProperties = if typeof objectProperties is "string" then objectProperties else JSON.stringify(objectProperties || {})
     params = {


got online/offline displaying correctly

got online/offline displaying correctly

--- src/controllers/main-controller.coffee
+++ src/controllers/main-controller.coffee
@@ -109,6 +109,7 @@
       return unless device
       LogService.add "#{device.name} ~#{device.uuid} is #{if data.online then 'online' else 'offline'}"
       device.online = data.online
+      $scope.updateDevice device

     $scope.$on 'gateblu:device:config', ($event, device) ->
       $scope.updateDevice device


Event more info

Event more info

--- test/appveyor.coffee
+++ test/appveyor.coffee
@@ -17,11 +17,14 @@
   data = JSON.stringify data
   uri.method = 'POST'
   uri.path = path
+  uri.pathname = path
   uri.headers =
     'Content-Type': 'application/json'
     'Content-Length': data.length
   console.log 'POST', api, uri, data
-  q = http.request uri
+  q = http.request uri, ->
+  q.on 'error', ->
+    console.error 'HTTP error!'
   q.end data

 events =


namespaced unbind

namespaced unbind

--- test/bindable.coffee
+++ test/bindable.coffee
@@ -47,7 +47,8 @@
   o.bind "test.TestNamespace", ->
     ok true

-  o.trigger "TestNamespace"
+  o.trigger ".TestNamespace"
+  o.trigger "test"

   o.unbind ".TestNamespace", ->
   o.trigger "test"


namespaced unbind

namespaced unbind

--- test/bindable.coffee
+++ test/bindable.coffee
@@ -55,12 +55,12 @@
 test "#unbind namespaced", 1, ->
   o = Object.extend({}, Bindable())

-  o.bind "TestNamespace.test", ->
+  o.bind "test.TestNamespace", ->
     ok true

   o.trigger "test"

-  o.unbind "TestNamespace.", ->
+  o.unbind "TestNamespace", ->
   o.trigger "test"

 module()


namespaced unbind

namespaced unbind

--- test/bindable.coffee
+++ test/bindable.coffee
@@ -46,7 +46,7 @@
 test "#unbind namespaced", ->
   o = Object.extend({}, Bindable())

-  o.bind "Test.test", ->
+  o.bind "test.Movable", ->

 module()


DOM element input value uses change event

--- src/dom_element/input_value_var.coffee
+++ src/dom_element/input_value_var.coffee
@@ -8,7 +8,7 @@
 module.exports = class InputValueVar extends Variable

   constructor: (@element) ->
-    @element.addEventListener 'input', =>
+    @element.addEventListener 'change', =>
       Transmission.start (tr) =>
         @originate(tr)


Still moving over libraries

Still moving over libraries

--- test/bodyTest.coffee
+++ test/bodyTest.coffee
@@ -95,7 +95,3 @@
     #body.findPart("head").should.equal body.torso.parts.head
     body.findPart("left eye").should.equal body.torso.parts.head.parts.face.parts.leftEye

-  it "should be able to hold something in it's hands", ->
-    body = harnessBody()
-    body.holdInHands
-      moveTo:global.$game.common.moveTo


make test more robust

make test more robust

--- test/celerity.coffee
+++ test/celerity.coffee
@@ -72,4 +72,4 @@
                         test.equals count, 0
                         test.done()

-                setTimeout next, 100
+                setTimeout next, 110


Fix broken test

Fix broken test

--- modules/form-builder/test/spec.coffee
+++ modules/form-builder/test/spec.coffee
@@ -52,7 +52,7 @@
     form.hidden('hd2').should.equal(true)

   it 'should allow html fields to be initialised with a value', ->
-    form = new hx.Form(hx.div())
+    form = new hx.Form(hx.detached('div').node())
       .addText('Text', { value: 'Something'})
       .addTextArea('Text Area', { value: 'Something Else'})
       .addEmail('Email', { value: 'a@a.a'})


Add test for message when receive webhook of post_created event

--- test/esa-test.coffee
+++ test/esa-test.coffee
@@ -173,6 +173,11 @@
           expect(emitted_data.user.screen_name).to.equal 'fukayatsu'
           expect(emitted_data.post.name).to.equal 'たいとる'

+        it 'sends message', ->
+          expect(room.messages).to.eql [
+            ['hubot', "fukayatsu created a new post: たいとる\n>Create post.\nhttps://example.esa.io/posts/1253"]
+          ]
+
     describe 'as invalid request', ->
       context 'with unkown User-Agent', ->
         beforeEach (done) ->


HUB-263: improve mocha test script

HUB-263: improve mocha test script

--- test/hTracker.coffee
+++ test/hTracker.coffee
@@ -27,50 +27,74 @@
 factory = require "../lib/hfactory"
 _ = require "underscore"

-topologies = []
-
-topology1 =
-  actor: config.logins[0].urn,
-  type: "hTracker"
-  children: []
-  properties:[]
-
-topology2 =
-  actor: config.logins[0].urn,
-  type: "hTracker"
-  children: []
-  properties:
-    db:
-      host: "mylocalhost",
-      port: 27017,
-      name: "test"
-    collection: "trackChannel"
-
-topology3 =
-  actor: config.logins[0].urn,
-  type: "hTracker"
-  children: []
-  properties:
-    channel:
-      actor: "urn:localhost:trackChannel",
-      type: "hchannel",
-      method: "inproc",
-      properties:
-        db:
-          host: "localhost",
-          port: 27017,
-          name: "test"
-        collection: "trackChannel"
-
-topologies.push topology1, topology2, topology3
-
-_.forEach topologies, (topology, index) =>
-  describe "hTracker - topology"+index, ->
-    hActor = undefined
-    hResultStatus = require("../lib/codes").hResultStatus
-    Tracket = require "../lib/actor/htracker"
-
+
+describe "hTracker", ->
+  hActor = undefined
+  hResultStatus = require("../lib/codes").hResultStatus
+  Tracket = require "../lib/actor/htracker"
+
+  describe "topology without channel", ->
     before () ->
+      topology =
+        actor: config.logins[0].urn,
+        type: "hTracker"
+        children: []
+        properties:[]
+
+      hActor = new Tracket topology
+
+    after () ->
+      #hActor.h_tearDown()
+      hActor = null
+
+    it "should automatically add the trackchannel if not set", (done) ->
+      if hActor.topology.children[0].type == "hchannel"
+        done()
+
+  describe "topology without channel but with db properties", ->
+    before () ->
+      topology =
+        actor: config.logins[0].urn,
+        type: "hTracker"
+        children: []
+        properties:
+          db:
+            host: "mylocalhost27017",
+            port: 27017,
+            name: "test"
+          collection: "trackChannel"
+
+      hActor = new Tracket topology
+
+    after () ->
+      #hActor.h_tearDown()
+      hActor = null
+
+    it "should automatically add the trackchannel if not set", (done) ->
+      if hActor.topology.children[0].type == "hchannel"
+        done()
+
+    it "should have the db properties specified in properties", (done) ->
+      if hActor.topology.children[0].properties.db.host == "mylocalhost27017"
+        done()
+
+  describe "other", ->
+    before () ->
+      topology =
+        actor: config.logins[0].urn,
+        type: "hTracker"
+        children: []
+        properties:
+          channel:
+            actor: "urn:localhost:trackChannel",
+            type: "hchannel",
+            method: "inproc",
+            properties:
+              db:
+                host: "localhost",
+                port: 27017,
+                name: "test"
+              collection: "trackChannel"

       hActor = new Tracket topology

@@ -79,15 +103,6 @@
     after () ->
       hActor.h_tearDown()
       hActor = null
-
-    describe "topology constructor", ->
-      it "should automatically add the trackchannel if not set", (done) ->
-        if hActor.topology.children[0].type == "hchannel"
-          done()
-
-      it "should have db properties", (done) ->
-        if hActor.topology.children[0].properties.db
-          done()

     describe "Peer-info", ->
       it "should add peer when receive peer-info", (done) ->


delete newscript.coffee

delete newscript.coffee

--- scripts/newscript.coffee
+++ scripts/newscript.coffee
@@ -1,2 +0,0 @@
-:q
-


filtered out emotes

filtered out emotes

--- scripts/sentiment.coffee
+++ scripts/sentiment.coffee
@@ -124,11 +124,11 @@
   # Params:
   #   Response
   logSentiment = (response) ->
-    analysis = sentiment(response.message.text)
-    if(response.message.text.length > 2 && analysis && analysis.score)
-      if !isPrivateMessage(response)
-        updateEntry('user', getUsername(response), getWeekOfYear(), analysis.score)
-        updateEntry('channel', getChannel(response), getWeekOfYear(), analysis.score)
+    # filter out slack emotes to their actual words
+    analysis = sentiment response.message.text.replace(/:(.*?)[_.\-](.*?):/g, ' $1 $2 ')
+    if response.message.text && analysis && analysis.score #&& !isPrivateMessage(response)
+      updateEntry('user', getUsername(response), getWeekOfYear(), analysis.score)
+      updateEntry('channel', getChannel(response), getWeekOfYear(), analysis.score)

   prettyPrintList = (entries, emptyMessage) ->
     output = ''


copy withoutProperties into project (gone in react), refactor

--- src/RouteToMixin.coffee
+++ src/RouteToMixin.coffee
@@ -1,7 +1,6 @@
 React = require 'react'
-withoutProperties = require 'react-router/modules/utils/withoutProperties'
+Navigation = require('react-router').Navigation;
 copyProperties = require 'react/lib/copyProperties'
-Navigation = require('react-router').Navigation;

 # A map of component props that are reserved for use by the
 # router and/or React. All other props are used as params that are
@@ -16,32 +15,32 @@

 isLeftClick = (event) -> event.button == 0
 isModifiedEvent = (event) -> !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey)
-
-getUnreservedProps = (props, additionalReservedProps) ->
-  if (additionalReservedProps)
-    reservedProps = copyProperties {}, RESERVED_PROPS
-    reservedProps = copyProperties reservedProps, additionalReservedProps
-  else
-    reservedProps = RESERVED_PROPS
-
-  withoutProperties props, reservedProps
+withoutProperties = (object, properties) ->
+  result = {}
+  for property of object
+    result[property] = object[property]  if object.hasOwnProperty(property) and not properties[property]
+  result

 RouteToMixin =
-  mixins: [Navigation]
+  mixins: [Navigation],

-  getUnreservedProps: getUnreservedProps
+  getUnreservedProps: (props, additionalReservedProps) ->
+    if (additionalReservedProps)
+      reservedProps = copyProperties {}, RESERVED_PROPS
+      reservedProps = copyProperties reservedProps, additionalReservedProps
+    else
+      reservedProps = RESERVED_PROPS
+
+    withoutProperties props, reservedProps

   propTypes:
     to: React.PropTypes.string.isRequired
     query: React.PropTypes.object

-  # Returns a hash of URL parameters to use in this <Component>'s path.
-  getParams: ->
-    getUnreservedProps @props, @additionalReservedProps
-
   # Returns the value of the "href" attribute to use on the DOM element.
   getHref: ->
-    this.makeHref @props.to, @getParams(), @props.query
+    allParams = withoutProperties @props, RESERVED_PROPS
+    this.makeHref @props.to, allParams, @props.query

   handleRouteTo: (event) ->
     if isModifiedEvent(event) or !isLeftClick(event)


Fix bug about saving battle timestamp

Fix bug about saving battle timestamp

--- views/api-resolver.coffee
+++ views/api-resolver.coffee
@@ -139,8 +139,12 @@
       when '/kcsapi/api_req_map/start'
         @_ships = window._ships
         @isStart = true
-      when '/kcsapi/api_req_map/next', \
-      '/kcsapi/api_req_sortie/battle', \
+        @battleStart = false
+      when '/kcsapi/api_req_map/next'
+        @_ships = window._ships
+        @nowDate = new Date().getTime()
+        @battleStart = false
+      when '/kcsapi/api_req_sortie/battle', \
       '/kcsapi/api_req_battle_midnight/sp_midnight', \
       '/kcsapi/api_req_sortie/airbattle', \
       '/kcsapi/api_req_battle_midnight/battle', \


Fix loadScript test on previous API versions

--- SwaggerImporter.coffee
+++ SwaggerImporter.coffee
@@ -1,5 +1,5 @@
-(loadScript or require)("tv4.js")
-(loadScript or require)("yaml.js")
+(this.loadScript or require)("tv4.js")
+(this.loadScript or require)("yaml.js")

 SwaggerImporter = ->


Disable Stylelint when no config is found.

--- configs/atom/config.cson
+++ configs/atom/config.cson
@@ -66,8 +66,7 @@
     codeStandardOrConfigFile: "WordPress"
     executablePath: "/Users/koop/.control/tools/phpcs/bin/phpcs"
     tabWidth: 4
-  "linter-stylelint":
-    disableWhenNoConfig: false
+  "linter-stylelint": {}
   "linter-ui-default":
     panelHeight: 69
   minimap: {}


Attempt to remove Underline support, but fail.

--- src/Editor.coffee
+++ src/Editor.coffee
@@ -20,6 +20,10 @@
         element: @elem
         pasteAsText: false
         mode: if enableRichText then Medium.richMode else Medium.inlineMode
+        modifiers:
+          b: "bold"
+          i: "italicize"
+          v: "paste"
     catch err
       console.error "Error setting up Medium:", err
       throw err


Changed the default values of the properties

--- src/Matrix.coffee
+++ src/Matrix.coffee
@@ -2,10 +2,10 @@
   constructor: (mat) ->
     switch arguments.length
       when 0
-        @xAxis = new DivSugar.Vector
-        @yAxis = new DivSugar.Vector
-        @zAxis = new DivSugar.Vector
-        @trans = new DivSugar.Vector
+        @xAxis = new DivSugar.Vector DivSugar.Vector.X_UNIT
+        @yAxis = new DivSugar.Vector DivSugar.Vector.Y_UNIT
+        @zAxis = new DivSugar.Vector DivSugar.Vector.Z_UNIT
+        @trans = new DivSugar.Vector DivSugar.Vector.ZERO

       when 1
         @xAxis = new DivSugar.Vector mat.xAxis
@@ -76,9 +76,7 @@
     cos = Math.cos deg * DivSugar.DEG_TO_RAD
     mat = DivSugar.Matrix._tmpMat1

-    mat.set 1, 0, 0, 0, cos, sin, 0, -sin, cos, 0, 0, 0
-    mat.toGlobal @
-
+    mat.set(1, 0, 0, 0, cos, sin, 0, -sin, cos, 0, 0, 0).toGlobal @
     @set mat

   rotateY: (deg) ->
@@ -86,9 +84,7 @@
     cos = Math.cos deg * DivSugar.DEG_TO_RAD
     mat = DivSugar.Matrix._tmpMat1

-    mat.set cos, 0, -sin, 0, 1, 0, sin, 0, cos, 0, 0, 0
-    mat.toGlobal @
-
+    mat.set(cos, 0, -sin, 0, 1, 0, sin, 0, cos, 0, 0, 0).toGlobal @
     @set mat

   rotateZ: (deg) ->
@@ -96,9 +92,7 @@
     cos = Math.cos deg * DivSugar.DEG_TO_RAD
     mat = DivSugar.Matrix._tmpMat1

-    mat.set cos, sin, 0, -sin, cos, 0, 0, 0, 1, 0, 0, 0
-    mat.toGlobal @
-
+    mat.set(cos, sin, 0, -sin, cos, 0, 0, 0, 1, 0, 0, 0).toGlobal @
     @set mat

   scale: (scaleX, scaleY, scaleZ) ->
@@ -218,8 +212,8 @@

 DivSugar.Matrix.UNIT = new DivSugar.Matrix 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0

-DivSugar.Matrix._tmpVec1 = new DivSugar.Vector
-DivSugar.Matrix._tmpVec2 = new DivSugar.Vector
-DivSugar.Matrix._tmpVec3 = new DivSugar.Vector
-DivSugar.Matrix._tmpMat1 = new DivSugar.Matrix
+DivSugar.Matrix._tmpVec1 = new DivSugar.Vector()
+DivSugar.Matrix._tmpVec2 = new DivSugar.Vector()
+DivSugar.Matrix._tmpVec3 = new DivSugar.Vector()
+DivSugar.Matrix._tmpMat1 = new DivSugar.Matrix()
 # _tmpQuat1 and _tmpQuat2 are defined in Quaternion.js


Added 'wrap' option

Added 'wrap' option

--- src/Roller.coffee
+++ src/Roller.coffee
@@ -138,6 +138,7 @@

 obfuscate = (source, options = {}) ->
 	options.headers ?= true
+	options.wrap ?= false

 	getNewVars = (varsSet, frames) ->
@@ -257,10 +258,12 @@
 		header += "#{literalsObject}\n" if literals.size
 		header += "#{globalsObject}\n" if globals.size

-		header + newSource
-	else
-		newSource
-
+		newSource = header + newSource
+
+	if options.wrap
+		newSource = "(function () {\n#{newSource}\n})();"
+
+	newSource

 window.roller ?= {}


Support latest affqis changes

Support latest affqis changes

--- src/affqis.coffee
+++ src/affqis.coffee
@@ -10,7 +10,8 @@
 # * `host`: {String} hostname of affqis server.
 # * `port`: {Number} port number of affqis server.
 # * `cb`: {Function} callback to get called with an error if we
-#         fail to establish a connection, or a session if we succeed.
+#         fail to establish a connection, or an {Object} with
+#         keys for `connection` and `session`
 connect = (realm, host, port, cb) ->
   affqisUrl = "ws://#{host}:#{port}/affqis"
   connection = new autobahn.Connection
@@ -18,8 +19,8 @@
     realm: realm
     max_retries: 0

-    cb(null, session)
   connection.onopen = (session) ->
+    cb(null, session: session, connection: connection)

   connection.onclose = (reason) =>
     switch reason
@@ -42,12 +43,8 @@
 # * `config`: {Object} with host, port, user, and optionally db keys.
 # * `cb`: {Function} Callback to be called with an error if we get a wamp
 #         failure, or if successful the connection id.
-connectJdbc = (session, {host, port, db, user}, cb) ->
-  if db?
-    connectArgs = [user, host, port, db]
-  else
-    connectArgs = [user, host, port]
-  session.call('connect', connectArgs)
+connectJdbc = ({session}, connectArgs, cb) ->
+  session.call('connect', [], connectArgs)
     .then((result) -> cb null, result)
     .catch(cb)

@@ -77,13 +74,14 @@
 # * `cb`: {Function} callback that'll get called with errors or our results.
 executeQuery = (session, id, hql, cb) ->
   rows = []
-  session.call('execute', [id, hql])
-   .then((topic) ->
-     session.subscribe(topic, ([event, row]) ->
-       if event == "row"
-         rows.push JSON.parse(row)
-       else
-         cb null,
+  session.call('execute', [], connectionId: id, sql: hql)
+  .then(({args: [topic, streamProc]}) ->
+    session.subscribe(topic, ([event, row]) ->
+      switch event
+        when "row" then rows.push JSON.parse(row)
+        when "update_count" then cb null, updateCount: row
+        else
+          cb null,
           rows: rows.map(normalizeRow)
           rowCount: rows.map.length
           fields: rows[0].map((data) -> _.pick data, ['name', 'type'])
@@ -106,7 +104,8 @@
     port: realmConfig.port
     user: realmConfig.user

-  {session: session, id: jdbcId}
+  session.id = jdbcId
+  session

 # Public: Disconnect Affqis's JDBC connection and the Affqis connection itself.
 #


Dates should be converted to JS dates

Dates should be converted to JS dates

--- src/affqis.coffee
+++ src/affqis.coffee
@@ -59,8 +59,8 @@
 normalizeRow = (row) ->
   addColumn = (acc, {name, type, value}) ->
     if type == "date" or type == "timestamp"
-      value = moment(value)
     acc[name.split('.')[1]] = value
+      value = moment(value).toDate()
     acc
   row.reduce(addColumn, {})


initial Docker control

initial Docker control

--- src/api/vm.coffee
+++ src/api/vm.coffee
@@ -1103,3 +1103,29 @@
 }

 exports.stats = stats;
+
+#---------------------------------------------------------------------
+# Actions on a Docker container in a VM
+# Can be: start, stop, pause, unpause, restart
+
+dockerContainerAction = $coroutine ({host, vm, container, action}) ->
+  xapi = @getXAPI vm
+  host = @getObject vm.$container
+  args = {
+    vmuuid: vm.UUID,
+    container: container,
+  }
+  console.log args
+  return $wait xapi.call 'host.call_plugin', host.ref, 'xscontainer', action, args
+
+dockerContainerAction.params = {
+  vm: { type: 'string' }
+  container: { type: 'string' }
+  action: { type: 'string' }
+}
+
+dockerContainerAction.resolve = {
+  vm: ['vm', 'VM'],
+}
+dockerContainerAction.permission = 'admin'
+exports.dockerContainerAction = docker


Added discover static method

Added discover static method

--- src/authrc.coffee
+++ src/authrc.coffee
@@ -47,7 +47,7 @@
     @file is getGlobalFilePath()

   @discover: ->
-    getAuthFilePath()
+    getAuthFilePath() or null

 getCurrentDirFilePath = (filepath) ->


memoize each property's accessor in a closure

--- src/batman.coffee
+++ src/batman.coffee
@@ -339,11 +339,12 @@
   hashKey: ->
     @hashKey = -> key
     key = "<Batman.Property base: #{Batman.Hash::hashKeyFor(@base)}, key: \"#{Batman.Hash::hashKeyFor(@key)}\">"
-
+
   accessor: ->
-    accessors = @base._batman?.get('keyAccessors')
-    if accessors && (val = accessors.get(@key))
-      return val
+    @accessor = -> accessor
+    keyAccessors = @base._batman?.get('keyAccessors')
+    accessor = if keyAccessors && (val = keyAccessors.get(@key))
+      val
     else
       @base._batman?.getFirst('defaultAccessor') or Batman.Property.defaultAccessor
   eachObserver: (iterator) ->
@@ -391,14 +392,14 @@
   _markNeedsRefresh: -> @_needsRefresh = true
   _handleSourceChange: @::refresh

-  valueFromAccessor: -> @accessor()?.get?.call(@base, @key)
+  valueFromAccessor: -> @accessor().get?.call(@base, @key)

   setValue: (val) ->
-    result = @accessor()?.set?.call(@base, @key, val)
+    result = @accessor().set?.call(@base, @key, val)
     @refresh()
     result
   unsetValue: ->
-    result = @accessor()?.unset?.call(@base, @key)
+    result = @accessor().unset?.call(@base, @key)
     @refresh()
     result


Guard against File object not existing in IE8

--- src/batman.coffee
+++ src/batman.coffee
@@ -1659,7 +1659,7 @@
     formData

   @dataHasFileUploads: dataHasFileUploads = (data) ->
-    return true if data instanceof File
+    return true if File? and data instanceof File
     type = $typeOf(data)
     switch type
       when 'Object'


undo changing baseclass

undo changing baseclass

--- framer/BaseClass.coffee
+++ framer/BaseClass.coffee
@@ -142,5 +142,5 @@
 					@_getPropertyDefaultValue(key)
 				)

-				if not (value in [undefined])
+				if not (value in [null, undefined])
 					@[key] = value


fix url

fix url

--- src/chance.coffee
+++ src/chance.coffee
@@ -39,5 +39,6 @@

   robot.hear /choose a card/i, (msg) ->
     card = msg.random deckOfCards
+    image_card_name = card.toLowerCase().replace /\s+/g, "_"
     msg.send card
-    msg.send 'http://nretnil.com/deck/' + card.toLowerCase().replace /\s+/g, "_" + '.png'
+    msg.send 'http://www.nretnil.com/deck/' + image_card_name + '.png'


More built-in coffee functions.

More built-in coffee functions.

--- src/coffee.coffee
+++ src/coffee.coffee
@@ -22,9 +22,11 @@
     'rt'
     'lt'
     'slide'
+    'move'
     'movexy'
     'moveto'
     'jump'
+    'jumpxy'
     'jumpto'
     'turnto'
     'home'
@@ -68,6 +70,8 @@
     'prompt'
     'done'
     'tick'
+    'type'
+    'log'
   ]

   VALUE_FUNCTIONS = [
@@ -108,6 +112,14 @@
     'rgb'
     'rgba'
     'cell'
+    '$'
+    'match'
+    'toString'
+    'charCodeAt'
+    'fromCharCode'
+    'split'
+    'join'
+    'sort'
   ]

   EITHER_FUNCTIONS = [
@@ -120,6 +132,8 @@
     'append'
     'finish'
     'loadscript'
+    'text'
+    'html'
   ]

   STATEMENT_KEYWORDS = [


Revert "New download URL", the old one still works and still has the newest archives. The new one only has 0.8.1+.

This reverts commit 50699b4712952f37c0166d04b382a8dcd23c39c0.

--- src/config.coffee
+++ src/config.coffee
@@ -26,7 +26,7 @@
 config.timeout = 10000 # ms before giving up and failing the test
 config.callbackURL = "http://127.0.0.1:#{config.httpPort}/callback"

-config.downloadURL = "http://dl.node-webkit.org/"
+config.downloadURL = "https://s3.amazonaws.com/node-webkit"

 if process.env.npm_package_config_sockport and process.env.npm_package_config_sockport isnt 'false'
 	config.sockPort = process.env.npm_package_config_sockport


Doc events in `Cursor`

Doc events in `Cursor`

--- src/cursor.coffee
+++ src/cursor.coffee
@@ -9,31 +9,6 @@
 #
 # Cursors belong to {Editor}s and have some metadata attached in the form
 # of a {Marker}.
-#
-# ## Events
-#
-# ### moved
-#
-# Extended: Emit when a cursor has been moved. If there are multiple cursors,
-# it will be emit for each cursor.
-#
-# * `event` {Object}
-#   * `oldBufferPosition` {Point}
-#   * `oldScreenPosition` {Point}
-#   * `newBufferPosition` {Point}
-#   * `newScreenPosition` {Point}
-#   * `textChanged` {Boolean}
-#
-# ### destroyed
-#
-# Extended: Emit when the cursor is destroyed
-#
-# ### visibility-changed
-#
-# Extended: Emit when the Cursor is hidden or shown
-#
-# * `visible` {Boolean} true when cursor is visible
-#
 module.exports =
 class Cursor extends Model
   screenPosition: null
@@ -79,11 +54,30 @@
       @emitter.dispose()
     @needsAutoscroll = true

+  # Essential: Calls your `callback` when the cursor has been moved.
+  #
+  # * `callback` {Function}
+  #   * `event` {Object}
+  #     * `oldBufferPosition` {Point}
+  #     * `oldScreenPosition` {Point}
+  #     * `newBufferPosition` {Point}
+  #     * `newScreenPosition` {Point}
+  #     * `textChanged` {Boolean}
   onDidChangePosition: (callback) ->
     @emitter.on 'did-change-position', callback

+  # Extended: Calls your `callback` when the cursor is destroyed
+  #
+  # * `callback` {Function}
   onDidDestroy: (callback) ->
     @emitter.on 'did-destroy', callback
+
+  # Extended: Calls your `callback` when the cursor's visibility has changed
+  #
+  # * `callback` {Function}
+  #   * `visibility` {Boolean}
+  onDidChangeVisibility: (callback) ->
+    @emitter.on 'did-change-visibility', callback

   on: (eventName) ->
     switch eventName
@@ -91,7 +85,10 @@
         Grim.deprecate("Use Cursor::onDidChangePosition instead")
       when 'destroyed'
         Grim.deprecate("Use Cursor::onDidDestroy instead")
-
+      when 'destroyed'
+        Grim.deprecate("Use Cursor::onDidDestroy instead")
+      else
+        Grim.deprecate("::on is no longer supported. Use the event subscription methods instead")
     super

   destroy: ->
@@ -153,6 +150,7 @@
       @visible = visible
       @needsAutoscroll ?= true if @visible and @isLastCursor()
       @emit 'visibility-changed', @visible
+      @emitter.emit 'did-change-visibility', @visible

   # Public: Returns the visibility of the cursor.
   isVisible: -> @visible


don't load list if already in scope or parent scope

--- lib/assets/javascripts/directives/ngautocomplete.js.coffee
+++ lib/assets/javascripts/directives/ngautocomplete.js.coffee
@@ -15,6 +15,7 @@
     @list        = angular.element("

")
     @filter      = $filter
     @listFactory = @element.injector().get(@factory)
+    @existing_factory = @scope[@factory] || @scope.$parent[@factory]
     @list.insertAfter(@element[0])

     if @parent_name
@@ -54,9 +55,15 @@
     updateView = @updateView
     if @context
       return unless @parent()
-    @listFactory.index @serialize(), (data) ->
-      scope[factory] = data
+    if @existing_factory
+      scope[factory] = @existing_factory
+      scope.$watchCollection factory, (newVal) ->
+        updateView(model())
       updateView(model())
+    else
+      @listFactory.index @serialize(), (data) ->
+        scope[factory] = data
+        updateView(model())
   updateView: (value) =>
     object              = {}
     object[@list_attr]  = value || ''
@@ -76,8 +83,8 @@
   # Private methods
   scope_assignment: (scope,arg) =>
     parent = scope
-    for scope in arg
-      parent = parent[scope] || parent.$parent[scope]
+    for level in arg
+      parent = parent[level] || parent.$parent[level]
     parent

 angular.module('AutoComplete', [ 'FactoryName'])


removing unused line

removing unused line

--- jsapp/xlform_model_view/model.surveyFragment.coffee
+++ jsapp/xlform_model_view/model.surveyFragment.coffee
@@ -157,7 +157,6 @@
       @rows = new Rows([], _parent: @)
       super(a,b)
       @rows.add __rows  if __rows
-      delete @__pendingRows
       for row in __rows
         row._parent = row.collection = @rows


Fix autolinking when more than one link

Fix autolinking when more than one link

--- lib/assets/javascripts/helpers/formatting.js.coffee
+++ lib/assets/javascripts/helpers/formatting.js.coffee
@@ -76,23 +76,24 @@
       return jsonml unless jsonml[0] is 'para'

       jsonml = parsePara jsonml, (item) ->
+        return item if item is 'para'
         urls = TentStatus.Helpers.extractUrlsWithIndices(String(item))
         return item unless urls.length
-        new_item = []
+
+        new_item = item.split('')
+        index_offset = 0
         for u in urls
-          before = item.slice(0, u.indices[0])
-          after = item.slice(u.indices[1], item.length)
+          before = new_item.slice(0, u.indices[0] - index_offset)
+          after = new_item.slice(u.indices[1] - index_offset)
+
           link = ['link', { href: u.url }, TentStatus.Helpers.truncate(TentStatus.Helpers.formatUrlWithPath(item.slice(u.indices[0], u.indices[1])), TentStatus.config.URL_TRIM_LENGTH)]
-          if before || after
-            new_item.push(
-              'span',
-              before,
-              link
-              after
-            )
-          else
-            new_item.push(link...)
-        new_item
+
+          before.push(link)
+          new_item = before.concat(after)
+
+          index_offset += u.indices[1] - u.indices[0] - 1
+
+        ['span'].concat(new_item)

       jsonml


Fixing scaffolding views

Fixing scaffolding views

--- lib/generators/pbw/templates/views/edit_view.coffee
+++ lib/generators/pbw/templates/views/edit_view.coffee
@@ -31,8 +31,10 @@
     )

   render: ->
-    @$el.html(@template(@model.toJSON() ))
-
-    this.$("form").backboneLink(@model)
-
+    @model.fetch
+      success: (model) =>
+        @$el.html(@template(model.toJSON() ))
+        this.$("form").backboneLink(@model)
+      error: (model, response)
+        debug response
     return this


modify json

modify json

--- src/web.coffee
+++ src/web.coffee
@@ -91,7 +91,7 @@

   html: (content,code=200) -> @write 'text/html', content, code
   text: (content) -> @write 'text/plain',content
-  json: (data, indent=null) -> @write 'application/json; charset=utf-8', JSON.stringify(data, ((k,v) -> if k.slice(0,1) is '_' then undefined else v), indent)
+  json: (data, indent=null) -> @write 'application/json; charset=utf-8', JSON.stringify(data, null, indent)
   redirect: (uri) ->
     @response.writeHead 302, 'Location' : uri
     @response.end()