export app, so it can be run in-process by the web server

--- app.coffee
+++ app.coffee
@@ -104,9 +104,8 @@
 	logger.error err: error, "server error"
 	res.sendStatus(error?.statusCode || 500)

-app.listen port = (Settings.internal?.clsi?.port or 3013), host = (Settings.internal?.clsi?.host or "localhost"), (error) ->
-	logger.info "CLSI starting up, listening on #{host}:#{port}"
+module.exports = {app:app}

-setInterval () ->
-	ProjectPersistenceManager.clearExpiredProjects()
-, tenMinutes = 10 * 60 * 1000
+#setInterval () ->
+#	ProjectPersistenceManager.clearExpiredProjects()
+#, tenMinutes = 10 * 60 * 1000


initializing the git repo...

initializing the git repo...

--- app.coffee
+++ app.coffee
@@ -1,10 +1,11 @@
 fs = require('fs')
 async = require('async')
 request = require('request')
+getuid = require('getuid')
 {exec} = require('child_process')

 API_ENDPOINT = 'http://paras.rulemotion.com:1337'
-HOME_PATH = '/home/haki'
+HAKI_PATH = '/home/haki'

 try
 	state = require('./state.json')
@@ -22,22 +23,20 @@
 	# bootstrapping
 	(config, callback) ->
 		request.post("#{API_ENDPOINT}/associate", {
-			user: config.id
+			json:
+				user: config.id
 		}, (error, response, body) ->
 			if error
 				return callback(error)

-			try
-				if typeof body isnt 'object'
-					throw new Error(body)
-
-				body = JSON.parse(body)
-			catch error
-				callback(error)
+			if typeof body isnt 'object'
+				callback(body)

 			state.virgin = false
 			state.uuid = body.uuid
 			state.giturl = body.giturl
+
+			console.log state

 			fs.writeFileSync('state.json', JSON.stringify(state))

@@ -54,27 +53,31 @@
 	(callback) -> async.waterfall(bootstrapTasks, callback)
 	(callback) -> exec('systemctl start openvpn@client', callback)
 	(callback) -> exec('systemctl enable openvpn@client', callback)
+	(callback) ->
+		process.setuid(getuid('haki'))
+		process.chdir(HAKI_PATH)
+		fs.mkdir('hakiapp', callback)
+	(callback) -> exec('git init', cwd: 'hakiapp', callback)
+	(callback) -> exec("git remote add origin #{state.giturl}", cwd: 'hakiapp', callback)
 ]

 stage2Tasks = [
-	(callback) -> process.chdir("#{HOME_PATH}/hakiapp")
-	(callback) -> exec('npm install', callback)
-	(callback) -> exec('foreman start', callback)
+	(callback) ->
+		process.setuid(getuid('haki'))
+		process.chdir(HAKI_PATH)
+		fs.mkdir('hakiapp', callback)
+	(callback) -> async.forever([
+		(callback) -> exec('git pull', cwd: 'hakiapp', callback)
+		(callback) -> exec('npm install', cwd: 'hakiapp', callback)
+		(callback) -> exec('foreman start', cwd: 'hakiapp', callback)
+	])
 ]

-async.series(stage1Tasks, ->
-	console.log('Bootstrapped')

-	async.doUntil(
-		-> fs.existsSync('hakiapp')
-		(callback) ->
-			process.chdir(HOME_PATH)
-			console.log('git clone')
-			exec("git clone #{state.giturl}")
-			setTimeout(callback, 1000)
-		(error) ->
-			if error?
-				console.error(error)
-			else
-				console.log('Initialized')
+if state.virgin
+	async.series(stage1Tasks, (error, results) ->
+		if (error)
+			console.error(error)
+		else
+			console.log('Bootstrapped')
 	)


make startup message consistent

make startup message consistent

--- app.coffee
+++ app.coffee
@@ -36,5 +36,5 @@
 	if error?
 		logger.error err: error, "could not start track-changes server"
 	else
-		logger.log "track changes api listening on http://#{host}:#{port}"
+		logger.info "trackchanges starting up, listening on #{host}:#{port}"


Added sendfile.it base

Added sendfile.it base

--- app.coffee
+++ app.coffee
@@ -8,4 +8,21 @@

 app.get '/', (req, resp) -> resp.render 'index'

-app.listen process.env.VMC_APP_PORT or 3000, -> console.log 'Listening...'+app.get '/:filename', (req, resp) ->
+  content = req.query['content']
+  if (content and content.length > 1024)
+    resp.json({error: 'File content is longer than 1024 characters and was truncated.. Please use POST instead of GET'}, 406)
+  else
+    saveit(req, resp)
+
+app.post '/:filename', saveit
+
+saveit = (req, resp) ->
+  content = req.query["content"]
+  unless (content)
+    resp.json({error: 'File content is missing'}, 406)
+  else
+    resp.attachment(req.params.filename)
+    resp.send(content)
+
+app.listen process.env.VMC_APP_PORT or 3000, -> console.log 'Listening...'


refactor

refactor

--- app.coffee
+++ app.coffee
@@ -16,7 +16,8 @@

 app.get '/', (req, res) ->
   twiml = new twilio.TwimlResponse()
-  twiml.say 'Yo, Yolk!'
+  twiml
+  .say 'Yo, Yolk!'
   .say 'Ringing yolk'
   .dial process.env.FORWARD,
     timeout: 8
@@ -29,12 +30,23 @@
   if req.body['DialCallStatus'] == 'completed'
     twiml.redirect '/keyin'
   else
-    twiml.say 'Trying YOLK again.'
+    twiml
+    .say 'Trying YOLK again.'
     .dial process.env.FORWARD,
       timeout: 10
       timeLimit: 5
       action: '/ring'
   send_xml res, twiml
+
+app.post '/gather', (req, res) ->
+  twiml = new twilio.TwimlResponse()
+  twiml
+  .say 'Doorbell Yolk!'
+    .gather
+      action: '/keyin'
+      numDigits: 4
+    , () ->
+      @.say 'yolk, please enter your code'

 app.post '/ring', (req, res) ->
   twiml = new twilio.TwimlResponse()
@@ -56,6 +68,12 @@
     .play '/DTMF-6.mp3',
       loop: 2
     .hangup
+  else if req.body['Digits'] == process.env.GUESTPIN
+    twiml
+    .say 'Welcom to the Breakfast House'
+    .play '/DTMF-6.mp3',
+      loop: 2
+    .hangup
   else
     twiml
     .say 'Denied!!'


statusMessage is null

statusMessage is null

--- src/dockerhub.coffee
+++ src/dockerhub.coffee
@@ -115,7 +115,7 @@
           if post_err
             res.send "エラーが発生しました: #{post_err}"
           else
-            res.send "#{post_res.statusCode} #{post_res.statusMessage}"
+            res.send post_res.statusCode
             for k,v of post_res.headers
               res.send "#{k}: #{v}"
             res.send "#{post_body}"


editor.coffee: made opts.debugger a local variable

--- client/editor.coffee
+++ client/editor.coffee
@@ -57,6 +57,7 @@
 module.exports = (e, opts) -> # opts contains callbacks to ide.coffee
   {id, emit} = opts
   ($e = $ e).html editorHTML
+  isDebugger = opts.debugger
   volatileLine = null # the line number of the empty line inserted when cursor is at eof and you press <down>

   cm = CodeMirror $e.find('.cm')[0],
@@ -93,8 +94,8 @@
   cm.dyalogCommands =
     ED: -> emit 'Edit', win: id, text: cm.getValue(), pos: cm.indexFromPos cm.getCursor(); return
     QT: -> emit 'CloseWindow', win: id; return
-    BK: -> (if opts.debugger then emit 'TraceBackward', win: id else cm.execCommand 'undo'); return # Backward or Undo
-    FD: -> (if opts.debugger then emit 'TraceForward', win: id else cm.execCommand 'redo'); return # Forward or Redo
+    BK: -> (if isDebugger then emit 'TraceBackward', win: id else cm.execCommand 'undo'); return # Backward or Undo
+    FD: -> (if isDebugger then emit 'TraceForward', win: id else cm.execCommand 'redo'); return # Forward or Redo
     SC: -> $tb.find('.tb-search:visible').focus(); return # Search
     EP: -> # Exit (and save changes)
       v = cm.getValue()
@@ -120,7 +121,7 @@
           cm.replaceRange s, {line: l, ch: 0}, {line: l, ch: cm.getLine(l).length}, 'D'
       return
     LN: -> # Toggle Line Numbers
-      v = !!if opts.debugger then prefs.lineNumbersInDebugger.toggle() else prefs.lineNumbersInEditor.toggle()
+      v = !!if isDebugger then prefs.lineNumbersInDebugger.toggle() else prefs.lineNumbersInEditor.toggle()
       cm.setOption 'lineNumbers', v; $tb.find('.tb-LN:visible').toggleClass 'pressed', v; return
     PV: -> search true; return # Previous
     NX: -> search(); return # Next
@@ -161,7 +162,7 @@
         cm.setCursor line: l, ch: 0
       return
     WI: opts.weakInterrupt
-    ER: -> (if opts.debugger then emit 'RunCurrentLine', win: id else cm.execCommand 'newlineAndIndent'); return
+    ER: -> (if isDebugger then emit 'RunCurrentLine', win: id else cm.execCommand 'newlineAndIndent'); return
     BH: -> emit 'ContinueTrace',  win: id; return # Run to exit (in tracer)
     RM: -> emit 'Continue',       win: id; return # Resume execution (in tracer)
     MA: -> emit 'RestartThreads', win: id; return # Resume all threads (in tracer)
@@ -172,7 +173,7 @@
         for l in [l0..l1] by 1
           if (i = breakpoints.indexOf l) >= 0 then breakpoints.splice i, 1; cm.setGutterMarker l, 'breakpoints', null
           else breakpoints.push l; cm.setGutterMarker l, 'breakpoints', createBreakpointElement()
-      if opts.debugger
+      if isDebugger
         emit 'SetLineAttributes', win: id, nLines: cm.lineCount(), lineAttributes: stop: breakpoints[..].sort (x, y) -> x - y
       return
   ###
@@ -260,11 +261,11 @@
     return

   setDebugger = (x) ->
-    opts.debugger = x; $('.debugger-toolbar', $e).toggle x; $('.editor-toolbar', $e).toggle !x
+    isDebugger = x; $('.debugger-toolbar', $e).toggle x; $('.editor-toolbar', $e).toggle !x
     cm.setOption 'readOnly', x; $('.CodeMirror', $e).toggleClass 'debugger', x; highlight null
-    ln = !!if opts.debugger then prefs.lineNumbersInDebugger() else prefs.lineNumbersInEditor()
+    ln = !!if isDebugger then prefs.lineNumbersInDebugger() else prefs.lineNumbersInEditor()
     cm.setOption 'lineNumbers', ln; $tb.find('.tb-LN:visible').toggleClass 'pressed', ln; return
-  setDebugger !!opts.debugger
+  setDebugger !!isDebugger

   scrollCursorIntoProminentView = -> # approximately to 1/3 of editor height; this might not work near the top or bottom
     h = $e.height(); {left, top} = cm.cursorCoords true, 'local'


Fix EventEmitter unit tests

Fix EventEmitter unit tests

Funny, none of these assertions were actually executed. I guess I was really drunk when I did this.

--- test/unit/events.spec.coffee
+++ test/unit/events.spec.coffee
@@ -32,13 +32,13 @@

       it 'should register all "on" methods to events', ->
         emitter.emit 'foo'
-        expect(object.onFoo).toHaveBeenCalled
+        expect(object.onFoo).toHaveBeenCalled()

         emitter.emit 'foo_bar'
-        expect(object.onFooBar).toHaveBeenCalled
+        expect(object.onFooBar).toHaveBeenCalled()

-        expect(object.foo).not.toHaveBeenCalled
-        expect(object.bar).not.toHaveBeenCalled
+        expect(object.foo).not.toHaveBeenCalled()
+        expect(object.bar).not.toHaveBeenCalled()

       it 'should bind methods to the owner object', ->
@@ -50,10 +50,10 @@

         emitter.emit 'foo'
         emitter.emit 'foo_bar'
-        expect(object.onFoo).toHaveBeenCalled
-        expect(object.onFooBar).toHaveBeenCalled
-        expect(object.foo).not.toHaveBeenCalled
-        expect(object.bar).not.toHaveBeenCalled
+        expect(object.onFoo).toHaveBeenCalled()
+        expect(object.onFooBar).toHaveBeenCalled()
+        expect(object.foo).not.toHaveBeenCalled()
+        expect(object.bar).not.toHaveBeenCalled()

   #============================================================================
@@ -68,4 +68,4 @@
       emitter.emit 'foo'
       emitter.emit 'bar'

-      expect(object.onFoo).toHaveBeenCalled
+      expect(object.onFoo).toHaveBeenCalled()


Solved unescaped special characters issue

--- app/assets/javascripts/cocoon/iceberg.js.coffee
+++ app/assets/javascripts/cocoon/iceberg.js.coffee
@@ -158,7 +158,7 @@
       form.find('.storing-fields').not('.removed-nested-fields').each ->
         zones.push
           quantity: $(this).find('input.storing-quantity').val()
-          unit: $(this).find('.storage-unit-name').html()
+          unit: $(this).find('.storage-unit-name').val()
           name: $(this).find('input.hidden').val()
       data = zones: zones


use new sketchily api to recheck svg features

--- app/assets/javascripts/image_question.js.coffee
+++ app/assets/javascripts/image_question.js.coffee
@@ -16,7 +16,12 @@
       autoOpen: false,
       width: 800,
       title: "Snapshot",
-      modal: true
+      modal: true,
+      open: =>
+        # re-check browser features and update unit calculations
+        canv = @get_svg_canvas()
+        if (canv && canv.recheckBrowserFeatures)
+          canv.recheckBrowserFeatures()
     })

     @shutterbug       = new Shutterbug(@interactive_selector, null,(image_tag)=>


pulling store_slug from product.data

pulling store_slug from product.data

--- app/assets/javascripts/mobile/_mobile.js.coffee
+++ app/assets/javascripts/mobile/_mobile.js.coffee
@@ -59,12 +59,13 @@

     fetch_product_images = ($self, callback) ->
         tag = $self.data('instagram-tag')
+        store_slug = $self.data('store-slug')
         $.ajax
             dataType: 'json'
             url: '/get_instagram_feed_for_user_and_filter_by_tag'
             data:
                 tag: tag
-                store_slug: gon.store_slug
+                store_slug: store_slug
             success: (response) =>
                 if response.status is 'error'
                     alert 'i don\'t know what to do yet'


added error message on failed loading #473

--- app/assets/javascripts/oxalis/controller.coffee
+++ app/assets/javascripts/oxalis/controller.coffee
@@ -58,88 +58,96 @@
     @urlManager = new UrlManager(@model)
     @model.set("state", @urlManager.initialState)

-    @model.fetch().done (error) =>
-
-      # Do not continue, when there was an error and we got no settings from the server
-      if error
-        return
-
-      unless @model.tracing.restrictions.allowAccess
-        Toast.Error "You are not allowed to access this tracing"
-        return
-
-      @urlManager.startUrlUpdater()
-
-      for allowedMode in @model.settings.allowedModes
-
-        @allowedModes.push switch allowedMode
-          when "oxalis" then constants.MODE_PLANE_TRACING
-          when "arbitrary" then constants.MODE_ARBITRARY
-          when "volume" then constants.MODE_VOLUME
-
-      if constants.MODE_ARBITRARY in @allowedModes
-        @allowedModes.push(constants.MODE_ARBITRARY_PLANE)
-
-      # FPS stats
-      stats = new Stats()
-      $("body").append stats.domElement
-
-      @sceneController = new SceneController(
-        @model.upperBoundary, @model.flycam, @model)
-
-
-      if @model.skeletonTracing?
-
-        @view = new SkeletonTracingView(@model)
-        @annotationController = new SkeletonTracingController(
-          @model, @sceneController, @view )
-        @planeController = new SkeletonTracingPlaneController(
-          @model, stats, @view, @sceneController, @annotationController)
-        @arbitraryController = new SkeletonTracingArbitraryController(
-          @model, stats, @view, @sceneController, @annotationController)
-
-      else if @model.volumeTracing?
-
-        @view = new VolumeTracingView(@model)
-        @annotationController = new VolumeTracingController(
-          @model, @sceneController, @view )
-        @planeController = new VolumeTracingPlaneController(
-          @model, stats, @view, @sceneController, @annotationController)
-
-      else # View mode
-
-        @view = new View(@model)
-        @planeController = new PlaneController(
-          @model, stats, @view, @sceneController)
-
-      @initKeyboard()
-
-      for binaryName of @model.binary
-        @listenTo(@model.binary[binaryName].cube, "bucketLoaded", -> app.vent.trigger("rerender"))
-
-      @listenTo(app.vent, "changeViewMode", @setMode)
-
-      @allowedModes.sort()
-      if @allowedModes.length == 0
-        Toast.error("There was no valid allowed tracing mode specified.")
-      else
-        app.vent.trigger("changeViewMode", @allowedModes[0])
-      if @urlManager.initialState.mode? and @urlManager.initialState.mode != @model.mode
-        app.vent.trigger("changeViewMode", @urlManager.initialState.mode)
-
-      # Zoom step warning
-      @zoomStepWarningToast = null
-      @model.flycam.on
-        zoomStepChanged : =>
-          shouldWarn = not @model.canDisplaySegmentationData()
-          if shouldWarn and not @zoomStepWarningToast?
-            toastType = if @model.volumeTracing? then "danger" else "info"
-            @zoomStepWarningToast = Toast.message(toastType,
-              "Segmentation data is only fully supported at a smaller zoom level.", true)
-          else if not shouldWarn and @zoomStepWarningToast?
-            @zoomStepWarningToast.remove()
-            @zoomStepWarningToast = null
-
+    @model.fetch()
+      .done( (error) => @modelFetchDone(error) )
+      .fail( (response) => @modelFetchFail(response) )
+
+  modelFetchDone : (error) ->
+
+    # Do not continue, when there was an error and we got no settings from the server
+    if error
+      return
+
+    unless @model.tracing.restrictions.allowAccess
+      Toast.Error "You are not allowed to access this tracing"
+      return
+
+    @urlManager.startUrlUpdater()
+
+    for allowedMode in @model.settings.allowedModes
+
+      @allowedModes.push switch allowedMode
+        when "oxalis" then constants.MODE_PLANE_TRACING
+        when "arbitrary" then constants.MODE_ARBITRARY
+        when "volume" then constants.MODE_VOLUME
+
+    if constants.MODE_ARBITRARY in @allowedModes
+      @allowedModes.push(constants.MODE_ARBITRARY_PLANE)
+
+    # FPS stats
+    stats = new Stats()
+    $("body").append stats.domElement
+
+    @sceneController = new SceneController(
+      @model.upperBoundary, @model.flycam, @model)
+
+
+    if @model.skeletonTracing?
+
+      @view = new SkeletonTracingView(@model)
+      @annotationController = new SkeletonTracingController(
+        @model, @sceneController, @view )
+      @planeController = new SkeletonTracingPlaneController(
+        @model, stats, @view, @sceneController, @annotationController)
+      @arbitraryController = new SkeletonTracingArbitraryController(
+        @model, stats, @view, @sceneController, @annotationController)
+
+    else if @model.volumeTracing?
+
+      @view = new VolumeTracingView(@model)
+      @annotationController = new VolumeTracingController(
+        @model, @sceneController, @view )
+      @planeController = new VolumeTracingPlaneController(
+        @model, stats, @view, @sceneController, @annotationController)
+
+    else # View mode
+
+      @view = new View(@model)
+      @planeController = new PlaneController(
+        @model, stats, @view, @sceneController)
+
+    @initKeyboard()
+
+    for binaryName of @model.binary
+      @listenTo(@model.binary[binaryName].cube, "bucketLoaded", -> app.vent.trigger("rerender"))
+
+    @listenTo(app.vent, "changeViewMode", @setMode)
+
+    @allowedModes.sort()
+    if @allowedModes.length == 0
+      Toast.error("There was no valid allowed tracing mode specified.")
+    else
+      app.vent.trigger("changeViewMode", @allowedModes[0])
+    if @urlManager.initialState.mode? and @urlManager.initialState.mode != @model.mode
+      app.vent.trigger("changeViewMode", @urlManager.initialState.mode)
+
+    # Zoom step warning
+    @zoomStepWarningToast = null
+    @model.flycam.on
+      zoomStepChanged : =>
+        shouldWarn = not @model.canDisplaySegmentationData()
+        if shouldWarn and not @zoomStepWarningToast?
+          toastType = if @model.volumeTracing? then "danger" else "info"
+          @zoomStepWarningToast = Toast.message(toastType,
+            "Segmentation data is only fully supported at a smaller zoom level.", true)
+        else if not shouldWarn and @zoomStepWarningToast?
+          @zoomStepWarningToast.remove()
+          @zoomStepWarningToast = null
+
+
+  modelFetchFail : (response) ->
+
+    Toast.error(response.responseJSON.messages[0].error)

   initKeyboard : ->


Fixing csv report links visualization

Fixing csv report links visualization

--- app/coffee/modules/admin/project-profile.coffee
+++ app/coffee/modules/admin/project-profile.coffee
@@ -25,6 +25,7 @@
 taiga = @.taiga

 mixOf = @.taiga.mixOf
+scopeDefer = @.taiga.scopeDefer
 trim = @.taiga.trim
 toString = @.taiga.toString
 joinStr = @.taiga.joinStr
@@ -101,7 +102,9 @@
         @scope.issueStatusList = _.sortBy(project.issue_statuses, "order")
         @scope.prioritiesList = _.sortBy(project.priorities, "order")
         @scope.severitiesList = _.sortBy(project.severities, "order")
-        @scope.$emit('project:loaded', project)
+
+        scopeDefer @scope, =>
+            @scope.$emit('project:loaded', project)

         @scope.projectTags = _.map @scope.project.tags, (it) =>
             return [it, @scope.project.tags_colors[it]]


Fix drag&drop stories from backlog to spritn

--- app/coffee/modules/resources/userstories.coffee
+++ app/coffee/modules/resources/userstories.coffee
@@ -112,17 +112,6 @@
         params = {project_id: projectId, bulk_stories: data}
         return $http.post(url, params)

-    service.bulkUpdateMilestone = (projectId, milestoneId, data) ->
-        url = $urls.resolve("bulk-update-us-milestone")
-        data = _.map data, (us) ->
-            return {
-                us_id: us.id
-                order: us.order
-            }
-
-        params = {project_id: projectId, milestone_id: milestoneId, bulk_stories: data}
-        return $http.post(url, params)
-
     service.listValues = (projectId, type) ->
         params = {"project": projectId}
         service.storeQueryParams(projectId, params)


fix time block bug

fix time block bug

I kind of think the real bug is being able to delete the last blank row,
but maybe we want people to be able to delete that

test plan:
- fill out a time range
- fill out a second time range
- delete the 3rd (blank) row
- click split

Change-Id: I8339bca9f84bf913b41a65446ef738d86b366cf6
Reviewed-on: https://gerrit.instructure.com/7945
Reviewed-by: Zach Wily <9de8c4480303b5335cd2a33eefe814615ba3612a@instructure.com>
Tested-by: Zach Wily <9de8c4480303b5335cd2a33eefe814615ba3612a@instructure.com>

--- app/coffeescripts/calendar/TimeBlockList.coffee
+++ app/coffeescripts/calendar/TimeBlockList.coffee
@@ -61,12 +61,9 @@
       input = event.target
       $input = $(input)
       unless @validField(input)
+        row = $input.closest('tr')
         # blank rows are OK
-        allBlank = true
-        for i in $input.closest('tr').find('input')
-          allBlank = false if i.value != ''
-
-        $input.addClass 'error' unless allBlank
+        $input.addClass 'error' unless @rowIsBlank(row)
       else
         $input.val $input.nextAll('.datetime_suggest').text()

@@ -109,10 +106,9 @@
       valid = true

       @blocksManager.reset()
-      lastRow = @div.find('.time-block-list-body tr:last').get(0)

       @div.find('.time-block-list-body').find('tr').each (i, row) =>
-        return if row == lastRow
+        return if @rowIsBlank(row)

         $row = $(row)
         for fieldName in ['date', 'start_time', 'end_time']
@@ -135,6 +131,12 @@
       invalidDate = $input.nextAll('.datetime_suggest').hasClass('invalid_datetime')
       !($input.val() == '') && !invalidDate

+    rowIsBlank: (row) ->
+      blank = true
+      for i in $(row).find('input')
+        blank = false if i.value != ''
+      blank
+
     blocks: () ->
       ary = []
       for range in @blocksManager.blocks


Inline spec results object

Inline spec results object

--- lib/jasmine-node/failure-tree.coffee
+++ lib/jasmine-node/failure-tree.coffee
@@ -10,8 +10,7 @@
   isEmpty: -> @suites.length is 0

   add: (spec) ->
-    result = spec.results()
-    for item in result.items_ when item.passed_ is false
+    for item in spec.results().items_ when item.passed_ is false
       failurePath = []
       parent = spec.suite
       while parent


Match jasmine files with version suffix

Match jasmine files with version suffix

--- lib/jasmine-node/failure-tree.coffee
+++ lib/jasmine-node/failure-tree.coffee
@@ -32,7 +32,7 @@
       @filterStackTrace(item)

   filterJasmineLines: (stackTraceLines) ->
-    jasminePattern = /^\s*at\s+.*\(?.*\/jasmine.js:\d+:\d+\)?\s*$/
+    jasminePattern = /^\s*at\s+.*\(?.*\/jasmine(-.*)?\.js:\d+:\d+\)?\s*$/

     index = 0
     while index < stackTraceLines.length


only include modules that have context functions

--- src/notebook.coffee
+++ src/notebook.coffee
@@ -22,7 +22,7 @@

   available_context_fns = (notebook) ->
-    result = _.object _.map notebook.modules, (module, name) -> [name, module.context_fns]
+    result = _.object _.filter _.map(notebook.modules, (module, name) -> [name, module.context_fns]), ([n, f]) -> f
     _.extend result, _.map(notebook.imports, (i) -> notebook.modules[i].context_fns)...


CoffeeScript 1.7.1

CoffeeScript 1.7.1

--- coffee-script.coffee
+++ coffee-script.coffee
@@ -12,7 +12,7 @@
 SourceMap     = require './sourcemap'

 # The current CoffeeScript version number.
-exports.VERSION = '1.7.0'
+exports.VERSION = '1.7.1'

 exports.FILE_EXTENSIONS = ['.coffee', '.litcoffee', '.coffee.md']


Changed log level to info on development

--- coffee/logger.coffee
+++ coffee/logger.coffee
@@ -2,8 +2,8 @@
 env = process.env.NODE_ENV
 path = require 'path'

-logfile = path.resolve "#{__dirname}/../logs/worker.log"
-level = if env is 'production' then 'info' else 'debug'
+logfile = path.resolve "#{__dirname}/../logs/kinesis.app.words.log"
+level = if env is 'production' then 'error' else 'info'

 Loggers =
 	transports :


avoid ssl cert errors

avoid ssl cert errors

--- unshrtn.coffee
+++ unshrtn.coffee
@@ -58,6 +58,7 @@
     url: short
     jar: jar
     timeout: 10000
+    strictSSL: false
     headers: {"User-Agent": userAgent short}
   sent = false
   try
@@ -84,7 +85,7 @@
     req.on 'error', (e) ->
       if not sent
         sent = true
-        next String(e), null
+        next String(e), short
   catch error
     if not sent
       sent = true


feat(web): сheck messages to an empty string before send;

--- apps/actor-web/app/services/actorService.coffee
+++ apps/actor-web/app/services/actorService.coffee
@@ -107,7 +107,6 @@
           @$rootScope.$broadcast 'actorAuthCode'
     , (tag, message, canTryAgain, state) ->
       console.log '[AW]ActorService requestSms: error: state:', state
-
   sendCode: (code) ->
     console.log '[AW]ActorService sendCode'
     @messenger.sendCode code, (state) =>
@@ -121,12 +120,14 @@
       console.log '[AW]ActorService sendCode: error: state:', state

-
   sendMessage: (peer, message) ->
     console.log '[AW]ActorService sendMessage'
-    # console.log '[AW]ActorService sendMessage: message:', message
+    console.log '[AW]ActorService sendMessage: message:', message
+    message = message.replace /^\s+|\s+$/g, ''
+    if message.length > 0
+      console.log '[AW]ActorService sendMessage: message:', message.length
     # console.log '[AW]ActorService sendMessage: peer:', peer
-    @messenger.sendMessage peer, message
+      @messenger.sendMessage peer, message

   # Events


Fix related spec

Fix related spec

--- apps/artwork/components/artist/test/view.coffee
+++ apps/artwork/components/artist/test/view.coffee
@@ -7,6 +7,7 @@
 { fabricate, fabricate2 } = require 'antigravity'
 { resolve } = require 'path'
 artists = require './fixture.coffee'
+Helpers = require '../../../helpers.coffee'

 describe 'ArtworkArtistView', ->

@@ -27,6 +28,7 @@
       sd: ARTWORK: @artwork
       _: _
       asset: (->)
+      helpers: Helpers
     }, =>
       ArtworkArtistView = rewire '../view.coffee'


Normalize all the line endings

Normalize all the line endings

--- keymaps/code-links.cson
+++ keymaps/code-links.cson
@@ -2,9 +2,9 @@
 # see a better solution. I couldn't extract which key is being pressed in the
 # event handler to match against the config.

-'body.code-links-ctrl atom-text-editor': {
-  'ctrl': 'code-links:tmp-show-links'
-}
+'body.code-links-ctrl atom-text-editor': {
+  'ctrl': 'code-links:tmp-show-links'
+}

 'body.code-links-alt atom-text-editor': {
   'alt': 'code-links:tmp-show-links'


Empty attribute objects no longer render with extra space

--- src/renderer.coffee
+++ src/renderer.coffee
@@ -60,7 +60,8 @@
 renderTag = (elem) ->
   tag = "<" + elem.name

-  if(elem.attribs)
+  if(elem.attribs and _.size(elem.attribs) > 0)
+
     tag += " " + utils.formatAttributes elem.attribs

   if !singleTag[elem.name]


Add tooltips for transparency buttons

Add tooltips for transparency buttons

--- lib/image-editor-view.coffee
+++ lib/image-editor-view.coffee
@@ -8,9 +8,9 @@
   @content: ->
     @div class: 'image-view', tabindex: -1, =>
       @div class: 'image-controls', outlet: 'imageControls', =>
-        @a class: 'image-controls-color-white', value: '#fff', =>
+        @a outlet: 'whiteTransparentBackgroundButton', class: 'image-controls-color-white', value: '#fff', =>
           @text 'white'
-        @a class: 'image-controls-color-black', value: '#000', =>
+        @a outlet: 'blackTransparentBackgroundButton', class: 'image-controls-color-black', value: '#000', =>
           @text 'black'
       @div class: 'image-container', =>
         @div class: 'image-container-cell', =>
@@ -31,6 +31,9 @@
     @command 'image-view:zoom-in', => @zoomIn()
     @command 'image-view:zoom-out', => @zoomOut()
     @command 'image-view:reset-zoom', => @resetZoom()
+
+    @whiteTransparentBackgroundButton.setTooltip("Use white transparent background")
+    @blackTransparentBackgroundButton.setTooltip("Use black transparent background")

   afterAttach: (onDom) ->
     return unless onDom


:arrow_up: Update minimal minimap version to 4.x

--- lib/minimap-selection.coffee
+++ lib/minimap-selection.coffee
@@ -9,7 +9,7 @@
     try
       atom.packages.activatePackage('minimap').then (minimapPackage) =>
         @minimap = minimapPackage.mainModule
-        return @deactivate() unless @minimap.versionMatch('>= 3.5.0')
+        return @deactivate() unless @minimap.versionMatch('4.x')

         @minimap.registerPlugin 'selection', this
     catch


Fixes #148

Fixes #148

--- lib/views/output-view.coffee
+++ lib/views/output-view.coffee
@@ -13,8 +13,8 @@

     initialize: ->
       super
-      atom.workspaceView.appendToBottom(this)
-      @subscribe $(window), 'core:cancel', => @detach()
+      @subscribe $(window), 'core:cancel', => @destroy()
+      @panel ?= atom.workspace.addBottomPanel(item: this)

     addLine: (line) ->
       @message += line
@@ -28,6 +28,6 @@
         @detach()
       , atom.config.get('git-plus.messageTimeout') * 1000

-    detach: ->
-      super
+    destroy: ->
+      @panel.destroy()
       @unsubscribe()


Throw a clear error when logging in with an invalid token

Change-Type: patch

--- lib/actions/auth.coffee
+++ lib/actions/auth.coffee
@@ -88,6 +88,11 @@
 						name: 'token'
 						type: 'input'
 				.then(resin.auth.loginWithToken)
+				.tap ->
+					resin.auth.whoami()
+					.then (username) ->
+						if !username
+							patterns.expectedError('Token authentication failed')
 			else if options.credentials
 				return patterns.authenticate(options)
 			else if options.web


do need units defaults for cicle, however

--- bokehjs/src/coffee/renderer/glyph/circle.coffee
+++ bokehjs/src/coffee/renderer/glyph/circle.coffee
@@ -194,6 +194,12 @@
         size: 4 # XXX: Circle should be a marker, then this wouldn't be necessary.
       }

+    defaults: ->
+      return _.extend {}, super(), {
+        size_units: 'screen'
+        radius_units: 'data'
+      }
+
   class Circles extends Glyph.Collection
     model: Circle


removed a console.log

removed a console.log

--- lib/dark-flat-ui.coffee
+++ lib/dark-flat-ui.coffee
@@ -13,7 +13,6 @@
     @sub.dispose()

   update: (config) ->
-    console.log config
     workspaceView = atom.views.getView atom.workspace
     if config.tabSyntaxThemeBackground
       workspaceView.classList.add 'dark-flat-ui-tab-syntax-background'


Refined version number usage and description comments

--- lib/flash-loader.coffee
+++ lib/flash-loader.coffee
@@ -133,11 +133,12 @@
     log "Loading Pepper Flash Player from: \n#{flashPath}"
     app.commandLine.appendSwitch 'ppapi-flash-path', flashPath
     # Note: the 'ppapi-flash-version' switch is used by Google Chrome to decide
-    # which flash player to load (it'll choose the newest one), and dioplay in
+    # which flash player to load (it'll choose the newest one), and display in
     # the 'chrome://version', 'chrome://plugins', 'chrome://flash' pages.
-    # But it's useless here, so we just ignore it.
-    if PLATFORM is 'darwin' or flashSources[usingIndex].ver?
-      ver = if PLATFORM is 'darwin' then getFlashVersion flashPath else flashSources[usingIndex].ver
+    # However, for (most) electron apps, it's useless. It's safe to ignore it.
+    ver = getFlashVersion flashPath
+    ver = flashSources[usingIndex].ver if not ver
+    if ver
       log "Pepper Flash Player version: #{ver}"
       app.commandLine.appendSwitch 'ppapi-flash-version', ver


Aggregate messages and call linter callback once

--- lib/linter-clang.coffee
+++ lib/linter-clang.coffee
@@ -158,22 +158,30 @@
     # options for BufferedProcess, same syntax with child_process.spawn
     options = {cwd: @cwd}

+    result = []
+
+    aggregate = (messages) ->
+      result = result.concat messages
+
     stdout = (output) =>
       if atom.inDevMode()
         console.log 'clang: stdout', output
       if @errorStream == 'stdout'
-        @processMessage(output, callback)
+        @processMessage(output, aggregate)

     stderr = (output) =>
       if atom.inDevMode()
         console.warn 'clang: stderr', output
       if @errorStream == 'stderr'
-        @processMessage(output, callback)
+        @processMessage(output, aggregate)

     if atom.inDevMode()
       console.log "linter-clang: command = #{command}, args = #{args}, options = #{options}"

-    new Process({command, args, options, stdout, stderr})
+    exit = () ->
+      callback result
+
+    new Process({command, args, options, stdout, stderr, exit})

   constructor: (editor) ->
     @editor = editor


Revert "accurate ping"

Revert "accurate ping"

This reverts commit 7006a7ebe79d6289435ba09e705f9454c42af821.

--- modules/ping.coffee
+++ modules/ping.coffee
@@ -7,8 +7,9 @@
     @pingCommand = @commands.registerCommand 'ping', pingOptions, @pingCommandFunction

   pingCommandFunction: (msg, args)->
+    sd = Date.now()
     @bot.sendMessage msg.channel, "Pong!"
-    .then (m)=> @bot.updateMessage m, "Pong! `#{m.timestamp - msg.timestamp}ms`"
+    .then (m)=> @bot.updateMessage m, "Pong! `#{Date.now() - sd}ms`"

   shutdown: =>
     @commands.unregisterCommand @pingCommand


Avoid $.data

Avoid $.data

--- lib/rails/remote.coffee
+++ lib/rails/remote.coffee
@@ -35,7 +35,7 @@

   # Allow AJAX method to be changed using the `data-method` attribute
   #   <a href="/comment" data-remote data-method="post">
-  if type = element.data 'method'
+  if type = element.attr 'data-method'
     settings.type = type

   # Use anchor href as the AJAX url


update

update

--- wrapper.coffee
+++ wrapper.coffee
@@ -6,7 +6,8 @@
 path = require 'path'
 extend = require 'extend'
 minimist = require 'minimist'
-bundle = require path.resolve './package.json'
+bundle = require './package.json'
+
 options = extend {}, {
   name:
     infix: '-'
@@ -15,7 +16,7 @@
   test: 'test'
   build: 'build'
   dist: 'dist'
-}, bundle.gulp?
+}, bundle?.gulp?

 wrapper = extend {}, gulp, {


lod_and_effect: update colors

lod_and_effect: update colors

--- lod_and_effect/lod_and_effect.coffee
+++ lod_and_effect/lod_and_effect.coffee
@@ -1,5 +1,5 @@
 # lod_and_effect.coffee
-#
+#
 # Interactive lod curve and QTL effect plot
 #
 # Click on chromosome in top panel for detailed view below
@@ -25,6 +25,9 @@
   for i in [0..3]
     hInner[i] = h - pad.top - pad.bottom
   chrGap = 8
+
+  blue = "darkslateblue"
+  red = "#d02090"

   wInner[1] = botLw - pad.left - pad.right
   botRw = (w - botLw)/2
@@ -270,8 +273,8 @@
          .attr("cy", (d) -> yScale[2](d))
          .attr("r", 6)
          .attr("fill", (d,i) ->
-            return "blue" if male[i]
-            "red")
+            return blue if male[i]
+            red)
          .attr("stroke", "black")
          .attr("stroke-width", "2")
          .on("mouseover", efftip)
@@ -333,14 +336,14 @@
                .attr("x", xpos[j])
                .attr("y", (top[j]+bottom[j])/2)
                .attr("transform", "rotate(270,#{xpos[j]},#{(top[j]+bottom[j])/2})")
-               .attr("fill", "blue")
+               .attr("fill", blue)

   # title on top panel
   topsvg.append("text")
      .text(data.phenotype)
      .attr("x", (left[0] + right[0])/2)
      .attr("y", pad.top/2)
-     .attr("fill", "blue")
+     .attr("fill", blue)

   # x-axis labels
   xlab = ["Chromosome", "Position (cM)"]
@@ -349,7 +352,7 @@
                .text(xlab[j])
                .attr("x", (left[j] + right[j])/2)
                .attr("y", bottom[j] + pad.bottom*0.65)
-               .attr("fill", "blue")
+               .attr("fill", blue)

   # lod curves by chr
   lodcurve = (j) ->
@@ -364,7 +367,7 @@
           .datum(data.lod[j].pos)
           .attr("d", lodcurve(j))
           .attr("class", "thickline")
-          .attr("stroke", "blue")
+          .attr("stroke", blue)
           .style("pointer-events", "none")

   # detailed LOD curves below
@@ -439,20 +442,20 @@
        .attr("d", botlodcurve(randomChr)(data.lod[randomChr].pos))
        .attr("class", "thickline")
        .attr("id", "detailedLod")
-       .attr("stroke", "blue")
+       .attr("stroke", blue)
        .style("pointer-events", "none")
   botsvg.append("text")
         .attr("x", (left[1] + right[1])/2)
         .attr("y", pad.top/2)
         .text("Chromosome #{randomChr}")
         .attr("id", "botLtitle")
-        .attr("fill", "blue")
+        .attr("fill", blue)
   botsvg.append("text")
         .attr("x", (left[2]+right[3])/2)
         .attr("y", pad.top/2)
         .text("")
         .attr("id", "botRtitle")
-        .attr("fill", "blue")
+        .attr("fill", blue)

   XaxisGrp[1].selectAll("empty")
               .data(xScale[1][randomChr].ticks(10))


Remove unused function

Remove unused function

--- lib/tab-bar-view.coffee
+++ lib/tab-bar-view.coffee
@@ -90,9 +90,6 @@
     RendererIpc.removeListener('tab:dropped', @onDropOnOtherWindow)
     @subscriptions.dispose()

-  isPreviewableItem: (item) ->
-    atom.config.get('tabs.usePreviewTabs') and item instanceof TextEditor
-
   setInitialPreviewTab: ->
     activeItem = @pane.getActiveItem()
     for tab in @getTabs() when tab.isPreviewTab


Remove tabs correctly when dropped on another window

--- lib/tab-bar-view.coffee
+++ lib/tab-bar-view.coffee
@@ -330,7 +330,7 @@
         else
           tab.element.parentElement.appendChild(placeholder)

-  onDropOnOtherWindow: (fromPaneId, fromItemIndex) ->
+  onDropOnOtherWindow: (event, fromPaneId, fromItemIndex) ->
     if @pane.id is fromPaneId
       if itemToRemove = @pane.getItems()[fromItemIndex]
         @pane.destroyItem(itemToRemove)


work on kdrouter

work on kdrouter

--- core/KDRouter.coffee
+++ core/KDRouter.coffee
@@ -10,14 +10,21 @@

   handleNotFound =(route)=> @handleNotFound? route

-  changeRoute =(frag, rewriteHashFragment=no)->
+  changeRoute =(frag, options={})->
+    {shouldPushState} = options
     node = tree
     params = {}
+    isRooted = '/' is frag[0]
+
     frag = frag.split '/'
     frag.shift() # first edge is garbage like '' or '#!'

     path = frag.join '/'
-    history.pushState {}, KDRouter.getTitle(path), path
+
+    if shouldPushState
+      history[if options.replaceState then 'replaceState' else 'pushState'](
+        {}, KDRouter.getTitle(path), "/#{path}"
+      )

     for edge in frag
       if node[edge]
@@ -31,28 +38,31 @@

     listeners = node[listenerKey]
     if listeners?.length
-      listener.call null, params for listener in listeners
+      listener.call @, params for listener in listeners

-  window.addEventListener 'popstate', (event)->
-    if location.pathname isnt currentPath
-      currentPath = location.pathname
-      changeRoute location.pathname, no
+  window.addEventListener 'popstate', (event)=>
+    currentPath = location.pathname
+    changeRoute.call @, location.pathname, shouldPushState: no

-  window.addEventListener 'hashchange', (event)->
-    changeRoute getHashFragment(event.newURL), yes
+  @routes = {}

   @getTitle =(path)-> path

-  @init =-> changeRoute location.hash.substr 1 if location.hash.length
-
+  @init =->
+    if location.hash.length
+      changeRoute.call @, location.hash.substr(1), shouldPushState: yes
+
   @handleNotFound =(route)-> log "The route #{route} was not found!"

-  @handleRoute =(route)-> changeRoute route, no
+  @handleRoute =(route, options={})->
+    options.shouldPushState ?= yes
+    changeRoute.call @, route, options

   @addRoutes =(routes)->
     @addRoute route, listener for own route, listener of routes

   @addRoute =(route, listener)->
+    @routes[route] = listener
     node = tree
     route = route.split('/')
     route.shift() # first edge is garbage like '' or '#!'


:fire: some copy :spaghetti:

:fire: some copy :spaghetti:

--- lib/text-objects.coffee
+++ lib/text-objects.coffee
@@ -1,7 +1,3 @@
-_ = require 'underscore-plus'
-{$$, Point, Range} = require 'atom'
-SearchViewModel = require './search-view-model'
-
 class TextObject
   constructor: (@editor, @state) ->


Require src value completion base on file name(app/controllers/*.js)

--- lib/viewProvider.coffee
+++ lib/viewProvider.coffee
@@ -175,10 +175,10 @@
   getAttributeValueCompletions: ({editor, bufferPosition}, prefix) ->
     tag = @getPreviousTag(editor, bufferPosition)
     attribute = @getPreviousAttribute(editor, bufferPosition)
+    completions = []

     # realted .tss
     if attribute == 'id' or attribute == 'class'
-      completions = []
       sourceEditor = util.getFileEditor related.getTargetPath('tss')
       if(!sourceEditor.isEmpty())
         values = tokenTextForSelector(sourceEditor.displayBuffer.tokenizedBuffer, attribute)
@@ -197,6 +197,17 @@
           completions.push @buildStyleSelectorCompletion(attribute, value, fileName)

       completions
+    if attribute is 'src'
+      if tag is 'Require'
+        arr = related.getTargetPath('js').split('/');
+        files = fs.readdirSync arr.slice(0,arr.length-1).join('/')
+        console.log files
+        for file in files
+          if path.extname(file) is '.js'
+            completions.push @buildAttributeValueCompletion(tag, attribute, file.split('.')[0])
+        completions
+      else if tag is 'Widget'
+        completions
     else
       values = @getAttributeValues(attribute)
       for value in values when not prefix or firstCharsEqual(value, prefix)


Use a filter to find objects by unique_id.

--- source/javascripts/views/canvas.coffee
+++ source/javascripts/views/canvas.coffee
@@ -33,15 +33,17 @@
         when "rect"    then @startRect(info.target, x, y)
         when "trash"   then @trashElement(info.target)

+  findObjectsByUniqueId: (target) ->
+    _.filter @objects, (obj) -> obj.id == target.unique_id
+
   trashElement: (target) ->
-    return unless target?
+    return if !target?

-    _.each @objects, (obj) ->
-      if obj.id == target.unique_id
-        if _.isFunction(obj.dispose)
-          obj.dispose()
-        else if _.isFunction(obj.remove)
-          obj.remove()
+    _.each @findObjectsByUniqueId(target), (obj) ->
+      if _.isFunction(obj.dispose)
+        obj.dispose()
+      else if _.isFunction(obj.remove)
+        obj.remove()

   startText: (target, x, y, e) ->
     return if target? # Only proceed if outside of an existing element.


reorg spec

reorg spec

--- spec/markdown-preview-view-spec.coffee
+++ spec/markdown-preview-view-spec.coffee
@@ -69,6 +69,11 @@
     beforeEach ->
       waitsForPromise ->
         preview.renderMarkdown()
+
+    it "removes line decorations on rendered code blocks", ->
+      editor = preview.find("atom-text-editor[data-grammar='text plain null-grammar']")
+      decorations = plainEditor[0].getModel().getDecorations(class: 'cursor-line', type: 'line')
+      expect(decorations.length).toBe 0

     describe "when the code block's fence name has a matching grammar", ->
       it "assigns the grammar on the atom-text-editor", ->
@@ -98,17 +103,6 @@
             return x++;
           }
         """
-
-  describe "editor behaves read-only", ->
-    beforeEach ->
-      waitsForPromise ->
-        preview.renderMarkdown()
-
-    describe "when a code block is rendered", ->
-      it "it does not have line-cursor decorations", ->
-        plainEditor = preview.find("atom-text-editor[data-grammar='text plain null-grammar']")
-        decorations = plainEditor[0].getModel().getDecorations(class: 'cursor-line', type: 'line')
-        expect(decorations.length).toBe 0

   describe "image resolving", ->
     beforeEach ->


:green_heart: Use a serif font to make sure char widths change

--- spec/text-editor-component-spec.coffee
+++ spec/text-editor-component-spec.coffee
@@ -2444,7 +2444,7 @@
         initialLineHeightInPixels = editor.getLineHeightInPixels()
         initialCharWidth = editor.getDefaultCharWidth()

-        component.setFontFamily('sans-serif')
+        component.setFontFamily('serif')
         expect(editor.getDefaultCharWidth()).toBe initialCharWidth

         wrapperView.show()
@@ -2453,7 +2453,7 @@
       it "does not re-measure character widths until the editor is shown again", ->
         wrapperView.hide()

-        component.setFontFamily('sans-serif')
+        component.setFontFamily('serif')

         wrapperView.show()
         editor.setCursorBufferPosition([0, Infinity])


Mechanics: Trick displays messages.

Mechanics: Trick displays messages.

--- data/bw/moves.coffee
+++ data/bw/moves.coffee
@@ -214,6 +214,9 @@
       tItem = target.getItem()
       user.setItem(tItem)
       target.setItem(uItem)
+      battle.message "#{user.name} switched items with its target!"
+      battle.message "#{user.name} obtained one #{tItem.name}!"
+      battle.message "#{target.name} obtained one #{uItem.name}!"

 makeExplosionMove = (name) ->
   extendMove name, ->


Clean SFX

Clean SFX

--- webpack/dist/sfx.coffee
+++ webpack/dist/sfx.coffee
@@ -9,14 +9,16 @@
   .sync

 PACKAGE = require '../../package'
+rm = require './rm'

 console.log "Creating sfx at", dst = path.resolve 'tmp/sfx'
+rm dst
 mkdirp dst

-zip = new yazl.ZipFile
-zip.outputStream.pipe fs.createWriteStream path.join dst, "#{PACKAGE.mingzi}.zip"
-
-zipTree = (zip, from)->
+zipTree = (name, from)->
+  console.log "Packing:", name += '.zip'
+  zip = new yazl.ZipFile
+  zip.outputStream.pipe fs.createWriteStream path.join dst, name
   do folder = (relative = '')->
     fs.readdirSync path.join from, relative
     .forEach (file)->
@@ -27,4 +29,4 @@
       zip.addFile full, short
   zip.end()

-zipTree zip, require './repo'
+zipTree PACKAGE.mingzi, require './repo'


added `csi test --testconfig` command

added `csi test --testconfig` command

--- csi.coffee
+++ csi.coffee
@@ -286,12 +286,24 @@

   test:
     description: """
-    start up a test server
+    start up a test server or get test parameters
     """
     action: () ->
       components = allComponents()
       if argv.listtests
         return listTests(discoverTests(argv.staticpath), argv.host, argv.port)
+      if argv.testconfig
+        return console.log(JSON.stringify(
+          components.reduce((config, component) ->
+            addQualifier = (name) ->
+              if component.json?.csi?[name]
+                config[name] ||= []
+                config[name].push component.json.csi[name]
+            addQualifier 'testExclude'
+            addQualifier 'testInclude'
+            return config
+          , {})
+          , null, "  "))
       commands.install.action()
       extraHtml = getTestTemplate() + "\n" + stringBundlesAsRequirejsModule()
       testServer.createServer(
@@ -568,6 +580,10 @@
       boolean: true
       describe: "just list tests\n(cmd: test)"

+    .option "testconfig",
+      boolean: true
+      describe: "get the configuration for the test server\n(cmd: test)"
+
     .option "templatepath",
       string: true
       alias: "t"


Shorten HKEY constant

Shorten HKEY constant

--- src/sys/admin.coffee
+++ src/sys/admin.coffee
@@ -1,5 +1,20 @@
 ###
 Test for Admin rights
 ###
+# const HKEY_CLASSES_ROOT = &H80000000
+# const HKEY_CURRENT_USER = &H80000001
+# const HKEY_LOCAL_MACHINE = &H80000002
+# const HKEY_USERS = &H80000003
+# const HKEY_CURRENT_CONFIG = &H80000005
+#
+# SID: S-1-5-18
+# Name: Local System
+# Description: A service account that is used by the operating system.
+# SID: S-1-5-19
+# Name: NT Authority
+# Description: Local Service
+# SID: S-1-5-20
+# Name: NT Authority
+# Description: Network Service
 module.exports = !GetObject "winmgmts://./root/default:StdRegProv"
-  .EnumKey 0x80000003, "S-1-5-20"
+  .EnumKey 1 << 31 | 3, "S-1-5-20"


Fixed #298 sending a form two times in popup

--- wzk/ui/form/ModalForm.coffee
+++ wzk/ui/form/ModalForm.coffee
@@ -7,6 +7,12 @@
 goog.require 'goog.events.Event'

 class wzk.ui.form.ModalForm extends goog.events.EventTarget
+
+  ###*
+    @enum {string}
+  ###
+  @CLS:
+    AJAX: 'ajax'

   ###*
     @enum {string}
@@ -45,7 +51,10 @@
     if response['snippets'][@form]
       @dialog.setContent response['snippets'][@form]
       el = @dialog.getContentElement()
-      @ajax.decorate @dom.one('form', el)
+      form = @dom.one('form', el)
+      if goog.dom.classes.has form, wzk.ui.form.ModalForm.CLS.AJAX
+        goog.dom.classes.remove form, wzk.ui.form.ModalForm.CLS.AJAX
+      @ajax.decorate form
       @register.process el
       @dialog.setVisible true


Fix IE writing-mode data

Fix IE writing-mode data

--- data/prefixes.coffee
+++ data/prefixes.coffee
@@ -390,7 +390,8 @@
           browsers: browsers

 # Writing Mode
-feature require('caniuse-db/features-json/css-writing-mode'), (browsers) ->
+writingMode = require('caniuse-db/features-json/css-writing-mode')
+feature writingMode, match: /a|x/, (browsers) ->
   prefix 'writing-mode',
           browsers: browsers


fix(vm.create): assign device to VDIs (#586)

--- src/api/vm.coffee
+++ src/api/vm.coffee
@@ -7,6 +7,7 @@
 endsWith = require 'lodash/endsWith'
 escapeStringRegexp = require 'escape-string-regexp'
 eventToPromise = require 'event-to-promise'
+maxBy = require 'lodash/maxBy'
 merge = require 'lodash/merge'
 sortBy = require 'lodash/sortBy'
 startsWith = require 'lodash/startsWith'
@@ -83,7 +84,9 @@
     vms: 1
   }
   vdiSizesByDevice = {}
+  highestDevice = -1
   forEach(xapi.getObject(template._xapiId).$VBDs, (vbd) =>
+    highestDevice = Math.max(highestDevice, vbd.userdevice)
     if (
       vbd.type is 'Disk' and
       (vdi = vbd.$VDI)
@@ -102,7 +105,7 @@
     limits.disk += size

     return $assign({}, vdi, {
-      device: vdi.userdevice ? vdi.device ? vdi.position,
+      device: ++highestDevice,
       size,
       SR: sr._xapiId,
       type: vdi.type
@@ -276,7 +279,6 @@
     items: {
       type: 'object'
       properties: {
-        device: { type: 'string' }
         size: { type: ['integer', 'string'] }
         SR: { type: 'string' }
         type: { type: 'string' }


add the parameter in the VM method

add the parameter in the VM method

--- src/api/vm.coffee
+++ src/api/vm.coffee
@@ -660,8 +660,11 @@
   return

 # TODO: integrate in xapi.js
-export_ = $coroutine ({vm, compress}) ->
-  stream = yield @getXAPI(vm).exportVm(vm.id, {compress: compress ? true})
+export_ = $coroutine ({vm, compress, onlyMetadata}) ->
+  stream = yield @getXAPI(vm).exportVm(vm.id, {
+    compress: compress ? true,
+    onlyMetadata: onlyMetadata ? false
+  })

   return {
     $getFrom: yield @registerHttpRequest(handleExport, {
@@ -673,6 +676,7 @@
 export_.params = {
   vm: { type: 'string' }
   compress: { type: 'boolean', optional: true }
+  onlyMetadata: { type: 'boolean', optional: false }
 }

 export_.resolve = {


removing eachObject method

removing eachObject method


follow docs

follow docs


Update minification options.

Update minification options.

--- src/minify.coffee
+++ src/minify.coffee
@@ -1,42 +1,52 @@
-detectMinifier = (minifier = 'esmangle') ->
+detectMinifier = (minifier) ->
   return 'uglify' if /^uglify/.test minifier

-  try
-    return 'uglify' if require.resolve 'uglify-js'
-  catch err
+  unless minifier?
+    try
+      return 'uglify' if require.resolve 'uglify-js'
+    catch err

-  try
-    require.resolve 'esmangle'
-  catch err
-    throw new Error 'Unable to determine minifier to use'
+    try
+      return 'esmangle' if require.resolve 'esmangle'
+    catch err
+      throw new Error 'Unable to determine minifier to use'

   minifier

 minifiers =
-  esmangle: (ast) ->
+  esmangle: (ast, opts = {}) ->
     esmangle  = require 'esmangle'

-    optimized = esmangle.optimize ast, null
-    mangled   = esmangle.mangle optimized
-    mangled
+    # Compress
+    ast = esmangle.optimize ast, null

-  uglify: (ast) ->
+    # Optionally mangle
+    if opts.mangle
+      ast = esmangle.mangle ast
+
+    ast
+
+  uglify: (ast, opts = {}) ->
     uglify = require 'uglify-js'

-    uglified = uglify.AST_Node.from_mozilla_ast ast
-    uglified.figure_out_scope()
+    uast = uglify.AST_Node.from_mozilla_ast ast
+    uast.figure_out_scope()

-    compressor = uglify.Compressor
+    # Compress
+    uast = uast.transform uglify.Compressor
       warnings: false

-    compressed = uglified.transform compressor
-    compressed.figure_out_scope()
-    compressed.compute_char_frequency()
-    compressed.mangle_names()
-    compressed.to_mozilla_ast()
+    # Optionally mangle
+    if opts.mangle
+      uast.figure_out_scope()
+      uast.compute_char_frequency()
+      uast.mangle_names()
+
+    uast.to_mozilla_ast()

 auto = (ast, opts) ->
   minifier = detectMinifier opts.minifier
+  console.log minifier
   minifiers[minifier] ast, opts

 wrapper           = auto


Separate vast error and empty responses

Separate vast error and empty responses

--- src/parser.coffee
+++ src/parser.coffee
@@ -20,7 +20,7 @@

     @parse: (url, headers, timeout, logger, cb) ->
         @_parse url, headers, timeout, logger, null, (err, response) ->
-            cb(response)
+            cb(err, response)

     @_parse: (url, headers, timeout, logger, parentURLs, cb) ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com


Update Input.coffee

Update Input.coffee


answer me when I ask you a question!

answer me when I ask you a question!

--- scripts/kerb.coffee
+++ scripts/kerb.coffee
@@ -68,16 +68,16 @@
       msg.send window.$(trader_panel).children("p").text()

   robot.respond /what(\')?s on kerb (on|next)?(.*)/i, (msg) ->
-    if msg.match[3] is 'tomorrow'
+    day_of_week = msg.match[3].replace(/[ \?]/g,'')
+    if day_of_week is 'tomorrow'
       today = new Date()
       day_of_week = days_of_week[today.getDay() + 1]
       displayTradersForDay(msg, false, day_of_week)
     else
       next_week = msg.match[2] == 'next'
-      day_of_week = msg.match[3].replace(' ','')
       day_of_week = day_of_week.charAt(0).toUpperCase() + day_of_week.slice(1)
       displayTradersForDay(msg, next_week, day_of_week)

-  robot.respond /what(\')?s on kerb$/i, (msg) ->
+  robot.respond /what(\')?s on kerb\??$/i, (msg) ->
     formatted_date = formatDate(new Date())
     tradersOnDate(msg, formatted_date)


fix link

fix link

--- query_runner.coffee
+++ query_runner.coffee
@@ -125,8 +125,8 @@
       @reply(
         attachments: [
           image_url: url
-          text: query.share_url
         ]
+        text: query.share_url
       )
     error = (error) =>
       @reply(":warning: #{error}")


Now with more :poop:

Now with more :poop:

--- scripts/vote.coffee
+++ scripts/vote.coffee
@@ -177,6 +177,7 @@
               "Butt evacuation in progress.",
               "http://i.imgur.com/ZTukQOl.gif",
               "http://i.imgur.com/0nwjhNO.gif",
+              "http://i.imgur.com/pm4DXWS.jpg"
             ]
           vote.start()
         catch error


Add additional yes/no answers to voting parser

Add additional / alternate language yes and no checking.

--- scripts/vote.coffee
+++ scripts/vote.coffee
@@ -139,14 +139,14 @@

     switch action
       # Vote on a current issue
-      when "yes"
+      when "yes","po","bai","da","si","ano","ja","jah","kylia","oui","vai","igen","taip","iva","tak","sim","ie","yog","ha","inde","ee","haa","oo","ya","yea","yeah","iya nih","jes"
         return msg.reply "There's no vote going on." unless Vote.current?
         result = Vote.current?.yes msg.message.user.name
         reply = "Your vote " +
           (if result then "totally counted." else "was absolutely worthless!")
         msg.reply reply

-      when "no"
+      when "no","jo","ne","ingen","nee","ei","non","ningunha","nein","nem","ekki","aon","nie","nao","nu","nej","dim","yox","yo'q","khong","geen","palibe","babu","ha ho","hakuna","ko si","akukho","dili","hindi","tidak","ora","tsy misy","tidak","kahore","neniu","pa gen okenn","nyet"
         return msg.reply "There's nothing to disagree with." unless Vote.current?
         result = Vote.current?.no msg.message.user.name
         reply = "Your vote " +


Fix status code in HEAD, which should match GET

Code 204 seems to cause Nginx reverse proxy to remove Content-Type,
which is problematic for Cocreate's image file detection.

--- server/files.coffee
+++ server/files.coffee
@@ -119,7 +119,7 @@
     if req.query.cache and not isNaN(parseInt(req.query.cache))
       headers['Cache-Control'] = "max-age=" + parseInt(req.query.cache)+", private"
     if req.method == 'HEAD'
-      res.writeHead 204, headers
+      res.writeHead statusCode, headers
       return res.end()
     if stream
       res.writeHead statusCode, headers


work on implement chinese remainder theorem

--- src/rsa.iced
+++ src/rsa.iced
@@ -14,8 +14,12 @@
 class Priv
   constructor : ({@p,@q,@d,@dmp1,@dmq1,@u,@pub}) ->

-  decrypt : (c) -> c.modPow @d, @pub.n
-  sign    : (m) -> m.modPow @d, @pub.n
+  #--------------------
+
+  decrypt : (c) -> @mod_pow c, @d
+  sign    : (m) -> @mod_pow m, @d
+
+  #--------------------

   serialize : () ->
     Buffer.concat [
@@ -25,9 +29,13 @@
       @u.to_mpi_buffer()
     ]

+  #--------------------
+
   n : () -> @p.multiply(@q)
   phi : () -> @p.subtract(BigInteger.ONE).multiply(@q.subtract(BigInteger.ONE))
   lambda : () -> @phi.divide(@p.subtract(BigInteger.ONE).gcd(@q.subtract(BigInteger.ONE)))
+
+  #--------------------

   @alloc : (raw, pub) ->
     orig_len = raw.length
@@ -40,6 +48,109 @@
       [d,p,q,u] = mpis
       [ null, new Priv({p,d,q,u,pub}) , (orig_len - raw.length) ]

+  #--------------------
+
+  # Use Chinese remainder theorem to compute (x^d mod n) quickly.
+  mod_pow : (x,d) ->
+
+    # pre-compute dP, dQ, and qInv if necessary
+    @dP = @d.mod(@p.subtract(BigInteger.ONE)) unless @dP?
+    @dQ = @d.mod(@q.subtract(BigInteger.ONE)) unless @dQ?
+    @qInv = @q.modInverse(@p) unless @qInv?
+
+    ### Chinese remainder theorem (CRT) states:
+
+      Suppose n1, n2, ..., nk are positive integers which are pairwise
+      coprime (n1 and n2 have no common factors other than 1). For any
+      integers x1, x2, ..., xk there exists an integer x solving the
+      system of simultaneous congruences (where ~= means modularly
+      congruent so a ~= b mod n means a mod n = b mod n):
+
+      x ~= x1 mod n1
+      x ~= x2 mod n2
+      ...
+      x ~= xk mod nk
+
+      This system of congruences has a single simultaneous solution x
+      between 0 and n - 1. Furthermore, each xk solution and x itself
+      is congruent modulo the product n = n1*n2*...*nk.
+      So x1 mod n = x2 mod n = xk mod n = x mod n.
+
+      The single simultaneous solution x can be solved with the following
+      equation:
+
+      x = sum(xi*ri*si) mod n where ri = n/ni and si = ri^-1 mod ni.
+
+      Where x is less than n, xi = x mod ni.
+
+      For RSA we are only concerned with k = 2. The modulus n = pq, where
+      p and q are coprime. The RSA decryption algorithm is:
+
+      y = x^d mod n
+
+      Given the above:
+
+      x1 = x^d mod p
+      r1 = n/p = q
+      s1 = q^-1 mod p
+      x2 = x^d mod q
+      r2 = n/q = p
+      s2 = p^-1 mod q
+
+      So y = (x1r1s1 + x2r2s2) mod n
+           = ((x^d mod p)q(q^-1 mod p) + (x^d mod q)p(p^-1 mod q)) mod n
+
+      According to Fermat's Little Theorem, if the modulus P is prime,
+      for any integer A not evenly divisible by P, A^(P-1) ~= 1 mod P.
+      Since A is not divisible by P it follows that if:
+      N ~= M mod (P - 1), then A^N mod P = A^M mod P. Therefore:
+
+      A^N mod P = A^(M mod (P - 1)) mod P. (The latter takes less effort
+      to calculate). In order to calculate x^d mod p more quickly the
+      exponent d mod (p - 1) is stored in the RSA private key (the same
+      is done for x^d mod q). These values are referred to as dP and dQ
+      respectively. Therefore we now have:
+
+      y = ((x^dP mod p)q(q^-1 mod p) + (x^dQ mod q)p(p^-1 mod q)) mod n
+
+      Since we'll be reducing x^dP by modulo p (same for q) we can also
+      reduce x by p (and q respectively) before hand. Therefore, let
+
+      xp = ((x mod p)^dP mod p), and
+      xq = ((x mod q)^dQ mod q), yielding:
+
+      y = (xp*q*(q^-1 mod p) + xq*p*(p^-1 mod q)) mod n
+
+      This can be further reduced to a simple algorithm that only
+      requires 1 inverse (the q inverse is used) to be used and stored.
+      The algorithm is called Garner's algorithm. If qInv is the
+      inverse of q, we simply calculate:
+
+      y = (qInv*(xp - xq) mod p) * q + xq
+
+      However, there are two further complications. First, we need to
+      ensure that xp > xq to prevent signed BigIntegers from being used
+      so we add p until this is true (since we will be mod'ing with
+      p anyway). Then, there is a known timing attack on algorithms
+      using the CRT. To mitigate this risk, "cryptographic blinding"
+      should be used (*Not yet implemented*). This requires simply
+      generating a random number r between 0 and n-1 and its inverse
+      and multiplying x by r^e before calculating y and then multiplying
+      y by r^-1 afterwards.
+    ###
+
+    # TODO: do cryptographic blinding
+
+    # calculate xp and xq
+    xp = x.mod(@p).modPow(@dP, @p)
+    xq = x.mod(@q).modPow(@dQ, @q)
+
+    # xp must be larger than xq to avoid signed bit usage
+    while xp.compareTo(xq) < 0
+      xp = xp.add @p
+
+    # do last step
+    xp.subtract(xq).multiply(@qInv).mod(@p).multiply(@q).add(xq)

 #=======================================================================

@@ -74,6 +185,10 @@
       @hash()[0...K.kid.len]
     ]
   ekid : () -> Buffer.concat [ new Buffer([K.kid.version, @type ] ), @hash() ]
+
+  #----------------
+
+  mod_pow : (x,d) -> x.modPow(d,@n)

 #=======================================================================


raspberrypi2.coffee: Set yocto version to thud

Signed-off-by: Andrei Gherzan <8dc7c09356db0b1762fb140617d7375c0a5a4131@balena.io>

--- raspberrypi2.coffee
+++ raspberrypi2.coffee
@@ -22,7 +22,7 @@
 		machine: 'raspberrypi2'
 		image: 'resin-image'
 		fstype: 'resinos-img'
-		version: 'yocto-sumo'
+		version: 'yocto-thud'
 		deployArtifact: 'resin-image-raspberrypi2.resinos-img'
 		compressed: true


project_server -- Fix ownership when writing file; improve documentation

--- salvus/project_server.coffee
+++ salvus/project_server.coffee
@@ -307,6 +307,8 @@
             socket.write_mesg('json', resp)
     )

+# Close the given project, which involves killing all processes of
+# this user and deleting all of their associated files.
 close_project = (socket, mesg) ->
     uname = username(mesg.project_uuid)
     async.series([
@@ -325,12 +327,14 @@
             socket.write_mesg('json', message.project_closed(id:mesg.id))
     )

+# Read a file in the given project.  This will result in an error if
+# the readFile function files, e.g., if the file doesn't exist or the
+# project isn't even open.  The read file is sent over socket as a blob.
 read_file_from_project = (socket, mesg) ->
-    path = "#{userpath(mesg.project_uuid)}/#{mesg.path}"
     data = undefined
     async.series([
         (cb) ->
-            fs.readFile(path, (err, _data) ->
+            fs.readFile("#{userpath(mesg.project_uuid)}/#{mesg.path}", (err, _data) ->
                 data = _data
                 cb(err)
             )
@@ -344,18 +348,31 @@
             socket.write_mesg('json', message.error(id:mesg.id, error:err))
     )

+# Write a file to the project
 write_file_to_project = (socket, mesg) ->
     data_uuid = mesg.data_uuid
+    if mesg.path.indexOf('..') != -1
+        # a sanity check, since we write the file as root first, and something bad could sneak in.
+        socket.write_mesg('json', message.error(id:mesg.id, error:'path must not contain ..'))
+        return
+    # Listen for the blob containg the actual content that we will write.
     write_file = (type, value) ->
         if type == 'blob' and value.uuid == data_uuid
             socket.removeListener(write_file)
             path = "#{userpath(mesg.project_uuid)}/#{mesg.path}"
-            fs.writeFile(path, value.blob, (err) ->
+            async.series([
+                (cb) ->
+                    fs.writeFile(path, value.blob, cb)
+                # Finally, set the permissions on the file to the correct user (instead of root)
+                (cb) ->
+                    child_process.exec("chown #{username(mesg.project_uuid)}. #{path}", cb)
+            ], (err) ->
                 if err
                     socket.write_mesg('json', message.error(id:mesg.id, error:err))
                 else
                     socket.write_mesg('json', message.file_written_to_project(id:mesg.id))
             )
+
     socket.on 'mesg', write_file

 server = net.createServer (socket) ->


spelling mistake of course

spelling mistake of course

--- scripts/ryanisawesome.coffee
+++ scripts/ryanisawesome.coffee
@@ -1,5 +1,5 @@
 # Commands:
-#   hubot ryan - repspons with a nice message
+#   hubot ryan - responds with a nice message


Added faction to player info

Added faction to player info

--- steam.coffee
+++ steam.coffee
@@ -137,7 +137,8 @@
             playerInfo = (communityID) ->
                 for player in match.players
                     if player.account_id is communityID
-                        msg.reply "#{getHero(player.hero_id).localized_name} (Lvl #{player.level}) | KDA: #{player.kills}/#{player.deaths}/#{player.assists} | LH: #{player.last_hits} | GPM: #{player.gold_per_min} | XPM: #{player.xp_per_min}"
+                        faction = if player.player_slot > 4 then 'Dire' else 'Radiant'
+                        msg.reply "#{getHero(player.hero_id).localized_name} (Lvl #{player.level}), #{faction} | KDA: #{player.kills}/#{player.deaths}/#{player.assists} | LH: #{player.last_hits} | GPM: #{player.gold_per_min} | XPM: #{player.xp_per_min}"
                         return
                 msg.reply "The Steam ID you have entered (\"#{msg.match[1]}\") was not found in Match ID #{match.match_id}."


Fixes sounds in 1_4_24

Fixes sounds in 1_4_24

--- src/scripts/one_four_twentyfour.coffee
+++ src/scripts/one_four_twentyfour.coffee
@@ -260,37 +260,32 @@

     @save()

-  playsound: (player,sound) ->
-    if @robot.bot?
-      @robot.bot.Room(player.room).sound sound, (err, data) =>
-        console.log "campfire error: #{err}" if err
-
   take: (msg, player, take) ->
     if @games[player.id]?
       game = @games[player.id]
       msg.reply "\n" + game.take(take)
-
+
       scoreValue = game.scoreValue()
       if scoreValue.taken.length == 4 and not (scoreValue.hasOne and scoreValue.hasFour)
-        @playsound player, "drama"
-
+        msg.play "drama"
+
       if game.gameover()
         if scoreValue.score == 24
           if scoreValue.taken.steps == 1
-            @playsound player, "yeah"
+            msg.play "yeah"
           else
-            @playsound player, "pushit"
+            msg.play "pushit"
         else if scoreValue.taken.steps == 1 and scoreValue.hasOne and scoreValue.hasFour
-          @playsound player, "live"
+          msg.play "live"
         else if scoreValue.score >= 22
-          @playsound player, "tada"
+          msg.play "tada"
         else if scoreValue.score >= 18
-          @playsound player, "greatjob"
+          msg.play "greatjob"
         else if scoreValue.score > 0
-          @playsound player, "crickets"
+          msg.play "crickets"
         else
-          @playsound player, "trombone"
-
+          msg.play "trombone"
+
         @playerdata[player.id].lastScore = scoreValue.score
         @playerdata[player.id].totalScore = scoreValue.score + @playerdata[player.id].totalScore
         @playerdata[player.id].totalGamesFinished = @playerdata[player.id].totalGamesFinished + 1


moved sockjs, broker and bongo above KDFramework, so KD can access to bongo's static methods immediately if needed

--- CakefileIncludes.coffee
+++ CakefileIncludes.coffee
@@ -7,140 +7,12 @@
     Server:
       Stuff:
         config          : "./empty.coffee"
-        # dependencies    : "./server/dependencies.coffee"
-        # errors          : "./server/app/errors.coffee"
-        # initConfig      : "./server/app/initConfig.coffee"
-        # emailer         : "./server/app/emailer.coffee"
-        # base            : "./server/app/base.coffee"
-        # utils           : "./server/app/utils.coffee"
-        #jspath          : "./server/app/jspath.coffee"
-        # access_control  : "./server/app/access_control.coffee"
-        # output_filters  : "./server/app/output_filters.coffee"
-        # api             : "./server/app/api.coffee"
-        # auth            : "./server/app/auth.coffee"
-        # exec            : "./server/app/exec.coffee"
-        # ftp             : "./server/app/ftp.coffee"
-        # job             : "./server/app/job.coffee"
-        # jobq            : "./server/app/jobq.coffee"
-        # resource        : "./server/app/resource.coffee"
-        # server          : "./server/app/server.coffee"
-        # foreign_auth    : "./server/app/foreign_auth.coffee"
-        # shell           : "./server/app/shell.coffee"
-        # status          : "./server/app/status.coffee"
-        # team            : "./server/app/team.coffee"
-        # Irc             : "./server/app/irc.coffee"
-
-
-        #KiteController  : "./server/app/core/KiteController.coffee"
-        # archiver        : "./server/app/core/archiver.coffee"
-        # limit           : "./server/app/core/limit.coffee"
-        # allocation      : "./server/app/core/allocation.coffee"
-        # register        : "./server/app/core/register.coffee"
-        #localization    : "./server/app/core/localization.coffee"
-        # relationship    : "./server/app/core/relationship.coffee"
-        # permission      : "./server/app/core/permission.coffee"
-        # tag             : "./server/app/core/taxonomy.coffee"
-        # uniqueid        : "./server/app/core/uniqueid.coffee"
-        # module          : "./server/app/core/module.coffee"
-        # moduleSelection : "./server/app/core/module.selection.coffee"
-        # modulecapsule   : "./server/app/core/modulecapsule.coffee"
-        # notifier        : "./server/app/core/notifier.coffee"
-        # session         : "./server/app/core/session.coffee"
-        # message         : "./server/app/core/message.coffee"
-        # messageNotifier : "./server/app/core/message.notifier.coffee"
-        # cronJob         : "./server/app/core/cronjob.coffee"
-
-        # flag                        : "./server/app/metadata/flag.coffee"
-        # metadata                    : "./server/app/metadata/metadata.coffee"
-        # metadataUniqueView          : "./server/app/metadata/metadata.uniqueview.coffee"
-        # metadataVote                : "./server/app/metadata/metadata.vote.coffee"
-        # metadataInappropriate       : "./server/app/metadata/metadata.inappropriate.coffee"
-        # metadataLike                : "./server/app/metadata/metadata.like.coffee"
-        # metadataExperiencepoints    : "./server/app/metadata/metadata.experiencepoints.coffee"
-
-      #Models :
-        # jchannel                  : "./server/app/bongo/models/channel.coffee"
-        # jemailnotification        : "./server/app/bongo/models/emailnotification.coffee"
-        # jemailconfirmation        : "./server/app/bongo/models/emailconfirmation.coffee"
-        # # bongo models:
-        # jactivity                 : "./server/app/bongo/models/activity/activity.coffee"
-        # jreplyactivity            : "./server/app/bongo/models/activity/reply.coffee"
-        # jabstractActivity         : "./server/app/bongo/models/activity/abstract.coffee"
-        # jstatusactivity           : "./server/app/bongo/models/activity/status.coffee"
-        # jcodeactivity             : "./server/app/bongo/models/activity/code.coffee"
-        # jquestionactivity         : "./server/app/bongo/models/activity/question.coffee"
-        # jdiscussionactivity       : "./server/app/bongo/models/activity/discussion.coffee"
-        # jlinkactivity             : "./server/app/bongo/models/activity/link.coffee"
-
-        # activity redo:
-
-
-        # # abstractions
-        # followable                : "./server/app/bongo/abstractions/followable.coffee"
-        # filterable                : "./server/app/bongo/abstractions/filterable.coffee"
-        # taggable                  : "./server/app/bongo/abstractions/taggable.coffee"
-        # notifying                 : "./server/app/bongo/abstractions/notifying.coffee"
-        # notifiable                : "./server/app/bongo/abstractions/notifiable.coffee"
-        # flaggable                 : "./server/app/bongo/abstractions/flaggable.coffee"
-
-
-        # cactivity                 : "./server/app/bongo/models/activity.coffee"
-        # cbucket                   : "./server/app/bongo/models/bucket.coffee"
-
-        # jlimit                    : "./server/app/bongo/models/limit.coffee"
-        # jmount                    : "./server/app/bongo/models/mount.coffee"
-        # jrepo                     : "./server/app/bongo/models/repo.coffee"
-        # jdatabase                 : "./server/app/bongo/models/database.coffee"
-        # jappstorage               : "./server/app/bongo/models/appStorage.coffee"
-        # jaccount                  : "./server/app/bongo/models/account.coffee"
-        # jsession                  : "./server/app/bongo/models/session.coffee"
-        # juser                     : "./server/app/bongo/models/user.coffee"
-        # jguest                    : "./server/app/bongo/models/guest.coffee"
-        # jvisitor                  : "./server/app/bongo/models/visitor.coffee"
-        # jhyperlink                : "./server/app/bongo/models/hyperlink.coffee"
-
-        # jregistrationpreferences  : "./server/app/bongo/models/registrationpreferences.coffee"
-
-        # # jterminal                  : "./server/app/bongo/models/terminal.coffee"
-
-        # fsWatcher                  : "./server/app/bongo/models/fsWatcher.coffee"
-
-        # jtag                      : "./server/app/bongo/models/tag.coffee"
-
-        # jcomment                  : "./server/app/bongo/models/messages/comment.coffee"
-        # jpost                     : "./server/app/bongo/models/messages/post.coffee"
-        # jstatusupdate             : "./server/app/bongo/models/messages/status.coffee"
-        # jcodesnip                 : "./server/app/bongo/models/messages/codesnip.coffee"
-        # janswer                   : "./server/app/bongo/models/messages/answer.coffee"
-        # jquestion                 : "./server/app/bongo/models/messages/question.coffee"
-        # jprivatemessage           : "./server/app/bongo/models/messages/privatemessage.coffee"
-        # japp                      : "./server/app/bongo/models/app.coffee"
-        # jreview                   : "./server/app/bongo/models/messages/review.coffee"
-
-        # jinvitation               : "./server/app/bongo/models/invitation.coffee"
-        # jinvitationrequest        : "./server/app/bongo/models/invitationrequest.coffee"
-        # jpasswordrecovery         : "./server/app/bongo/models/passwordrecovery.coffee"
-        # jkitecluster              : "./server/app/bongo/models/kitecontroller/kitecluster.coffee"
-        # jkiteconnection           : "./server/app/bongo/models/kitecontroller/kiteconnection.coffee"
-
-
-      # OtherStuff :
-        # moduledata            : "./server/app/core/moduledata.coffee"
-        # moduledata_deprecated : "./server/app/core/moduledata_deprecated.coffee"
-        # defaultAllocations    : "./server/app/defaults/defaultallocations.coffee"
-        # defaultProducts       : "./server/app/defaults/defaultproducts.coffee"
-        # migrant               : "./server/migrate/migrant.coffee"
-        # mysqlMigrant          : "./server/migrate/mysqlmigrant.coffee"
-        # ohlohDump             : "./server/migrate/migrants/ohlohdump.coffee"
-        # userDump              : "./server/migrate/migrants/userdump.coffee"
-        # groupsDump            : "./server/migrate/migrants/groupsdump.coffee"
-        # postsDump             : "./server/migrate/migrants/postsdump.coffee"
-        # unwrapper             : "./server/migrate/migrants/unwrapper.coffee"
-        # main                  : "./server/app/main.coffee"

     Client:
       Framework :
-
+        sockjs                      : "./client/libs/sockjs-0.3-patched.js"
+        broker                      : "./node_modules/broker-client/browser/broker.js"
+        bongojs                     : "./node_modules/bongo-client/browser/bongo.js"
         # core
         __utils             : "./client/Framework/core/utils.coffee"
         KD                  : "./client/Framework/core/KD.coffee"
@@ -501,9 +373,6 @@
         sharedRoutes                : "./routes/index.coffee"
         kodingrouter                : "./client/app/MainApp/kodingrouter.coffee"
         #broker                      : "./broker/apps/broker/priv/www/js/broker.js"
-        sockjs                      : "./client/libs/sockjs-0.3-patched.js"
-        broker                      : "./node_modules/broker-client/browser/broker.js"
-        bongojs                     : "./node_modules/bongo-client/browser/bongo.js"
         bongo_mq                    : "./client/app/MainApp/mq.config.coffee"
         pistachio                   : "./node_modules/pistachio/browser/pistachio.js"


Added test download

Added test download

--- server/cron/downloadMaterials.coffee
+++ server/cron/downloadMaterials.coffee
@@ -2,6 +2,8 @@

 import Material from "../models/Material"
 import download from '../lib/download'
+
+download 'http://olympiads.nnov.ru/qwerty'

 import logger from '../log'


Update Checkin.coffee

Update Checkin.coffee

--- server/models/Checkin.coffee
+++ server/models/Checkin.coffee
@@ -37,4 +37,4 @@

 export default Checkin

-export MAX_CHECKIN_PER_SESSION = [13, 21]
+export MAX_CHECKIN_PER_SESSION = [21, 21]


show colors for Newbie and newbie class

show colors for Newbie and newbie class

--- shared/IdleController.coffee
+++ shared/IdleController.coffee
@@ -141,7 +141,7 @@

       $scope.classToColor = (itemClass) ->
         switch itemClass
-          when 'newbie'           then return 'bg-maroon'
+          when 'newbie', 'Newbie' then return 'bg-maroon'
           when 'Normal', 'basic'  then return 'bg-gray'
           when 'pro'              then return 'bg-purple'
           when 'idle'             then return 'bg-rainbow'


Fix pinch scale

Fix pinch scale

--- framer/GestureInputRecognizer.coffee
+++ framer/GestureInputRecognizer.coffee
@@ -344,7 +344,7 @@
 			touchOffset: {x:0, y:0} # Offset between two fingers √
 			touchDistance: 0 # Distance between two fingers √
 			scale: 1 # Scale value from two fingers √
-			scaleDirection: null # Direction for scale: up or down
+			scaleDirection: null # Direction for scale: up or down √
 			rotation: 0 # Rotation value from two fingers √

 		# Properties relative to a start event
@@ -382,65 +382,16 @@
 				return e.time > (event.time - (GestureInputVelocityTime * 1000))
 			event.velocity = @_getVelocity(events)

-		if @session?.lastEvent
-			if @session.lastEvent.fingers == 1 and event.fingers == 2
-				print "1 -> 2"
-			if @session.lastEvent.fingers == 2 and event.fingers == 1
-				print "2 -> 1"
-
-
-
-
-
-		# if event.fingers > 0
-		# 	if @session?.startEvent
-		# 		pointA = @_getTouchPoint(@session.startEvent, 0)
-		# 		pointB = @_getTouchPoint(event, 0)
-		# 		event.angle = Utils.pointAngle(pointA, pointB)
-		# 		event.touchOffset = Utils.pointSubtract(pointA, pointB)
-
-		# if event.fingers > 1
-		# 	pointA = @_getTouchPoint(event, 0)
-		# 	pointB = @_getTouchPoint(event, 1)
-		# 	event.touchCenter = Utils.pointCenter(pointB, pointA)
-		# 	event.touchDistance = Utils.pointDistance(pointA, pointB)
-		# 	event.rotation = Utils.pointAngle(pointA, pointB)
-
-		# 	# if @session?.lastEvent
-		# 	# 	if Math.abs(event.rotation - @session.lastEvent.rotation) >= 180
-		# 	# 		print "Oh no"
-
+		# Scale can only be set after we started a pinch session
+		if @session?.started.pinch
+			event.scale = event.touchDistance / @session.started.pinch.touchDistance
+			event.scaleDirection = @_getScaleDirection(event.scale - @session.lastEvent.scale)

 		# if @session?.lastEvent
-		# 	# If the amount of fingers changed we don't send any delta
-		# 	if @session.lastEvent.fingers != event.fingers
-		# 		event.delta = {x:0, y:0}
-
-		# 	# If there are exactly two fingers we use their center point as delta,
-		# 	# this works because we use the single finger point as center when there
-		# 	# is only one finger in this touch.
-		# 	else
-		# 		event.delta = Utils.pointSubtract(event.touchCenter, @session.lastEvent.touchCenter)
-		# 		event.deltaAngle = Utils.pointAngle(@session.lastEvent.touchCenter, event.touchCenter)
-
-		# 	event.deltaDirection = @_getDirection(event.delta)
-
-		# if @session?.started.pinch
-		# 	event.scale = event.touchDistance / @session.started.pinch.touchDistance
-
-		# 	if @session?.lastEvent
-		# 		event.scaleDirection = @_getScaleDirection(event.scale - @session.lastEvent.scale)
-
-		# if @session?.force
-		# 	event.force = @session.force
-
-		# event.offset = Utils.pointSubtract(event.point, event.start)
-		# event.offsetAngle = Utils.pointAngle(event.start, event.point)
-		# event.offsetDirection = @_getDirection(event.offset)
-
-		# # if @session.lastEvent?
-		# # 	if @session.lastEvent.fingers == 1 and event.fingers == 2
-		# # 		event.offset = Utils.pointSubtract(event.offset, event.touchOff)
+		# 	if @session.lastEvent.fingers == 1 and event.fingers == 2
+		# 		print "1 -> 2"
+		# 	if @session.lastEvent.fingers == 2 and event.fingers == 1
+		# 		print "2 -> 1"

 		# Convert point style event properties to dom style:
 		# event.delta -> event.deltaX, event.deltaY


Indentation

Indentation

--- command.coffee
+++ command.coffee
@@ -9,9 +9,9 @@
 class Command
   constructor: ->
     @adapters = []
-    @buses = []
-    @cores = []
-    @queues = []
+    @buses    = []
+    @cores    = []
+    @queues   = []

   parseInt: (arg) => parseInt arg


Fix index name

Fix index name

--- command.coffee
+++ command.coffee
@@ -38,7 +38,7 @@
     uri = url.format
       protocol: 'http'
       host: @destinationElasticsearchUrl
-      pathname: "/flow_success_rates/event/"
+      pathname: "/flow_success_rate/event/"

     request.post uri, json: deployment, (error, response, body) =>
       return callback error if error?


Allow required files to change the input and output of compiled files.

--- command.coffee
+++ command.coffee
@@ -98,20 +98,22 @@
   if o.require
     require fs.realpathSync file for file in o.require
   try
-    CoffeeScript.emit 'compile', {source, code, base, options}
+    CoffeeScript.emit 'compile', task = {source, code, base, options, codeOpts}
+    {source, code, base, options, codeOpts} = task if CoffeeScript.listeners 'compile'
     if      o.tokens      then printTokens CoffeeScript.tokens code
     else if o.nodes       then puts CoffeeScript.nodes(code).toString()
     else if o.run         then CoffeeScript.run code, codeOpts
     else
-      js = CoffeeScript.compile code, codeOpts
-      CoffeeScript.emit 'success', js
+      js = task.js = CoffeeScript.compile code, codeOpts
+      CoffeeScript.emit 'success', task
+      {js} = task if CoffeeScript.listeners 'success'
       if o.print          then print js
       else if o.compile   then writeJs source, js, base
       else if o.lint      then lint js
   catch err
     # Avoid using 'error' as it is a special event -- if there is no handler,
     # node will print a stack trace and exit the program.
-    CoffeeScript.emit 'failure', err
+    CoffeeScript.emit 'failure', err, task
     return if CoffeeScript.listeners('failure').length
     error(err.stack) and process.exit 1 unless o.watch
     puts err.message


Removed dependency on the util module in the coffee source files

--- command.coffee
+++ command.coffee
@@ -7,7 +7,6 @@
 # External dependencies.
 fs             = require 'fs'
 path           = require 'path'
-util           = require 'util'
 helpers        = require './helpers'
 optparse       = require './optparse'
 CoffeeScript   = require './coffee-script'
@@ -164,7 +163,7 @@
     js = ' ' if js.length <= 0
     fs.writeFile jsPath, js, (err) ->
       if err then printLine err.message
-      else if opts.compile and opts.watch then util.log "compiled #{source}"
+      else if opts.compile and opts.watch then console.log "compiled #{source}"
   path.exists dir, (exists) ->
     if exists then compile() else exec "mkdir -p #{dir}", compile


fix 'litcoffee' extension support

fix 'litcoffee' extension support

As pointed out by @satyr in 27551b23f3e8b3568b97b823226b89ed9c28802e

--- command.coffee
+++ command.coffee
@@ -88,7 +88,7 @@
   fs.stat source, (err, stats) ->
     throw err if err and err.code isnt 'ENOENT'
     if err?.code is 'ENOENT'
-      if topLevel and source[-7..] not in coffee_exts
+      if topLevel and path.extname(source) not in coffee_exts
         source = sources[sources.indexOf(source)] = "#{source}.coffee"
         return compilePath source, topLevel, base
       if topLevel


Results now include a schema

Results now include a schema

--- spec/network.coffee
+++ spec/network.coffee
@@ -266,12 +266,14 @@
               chai.expect(msg.payload.inPorts).to.eql [
                 id: 'in'
                 type: 'all'
+                schema: null
                 required: false
                 addressable: false
                 description: 'Packet to be printed through console.log'
               ,
                 id: 'options'
                 type: 'object'
+                schema: null
                 required: false
                 addressable: false
                 description: 'Options to be passed to console.log'
@@ -279,6 +281,7 @@
               chai.expect(msg.payload.outPorts).to.eql [
                 id: 'out'
                 type: 'all'
+                schema: null
                 required: false
                 addressable: false
               ]


More case sensitive requires

More case sensitive requires

--- test/hearts/service/handlerTest.coffee
+++ test/hearts/service/handlerTest.coffee
@@ -4,7 +4,7 @@
 Handler = require '../../../lib/hearts/service/handler'
 mapper = require '../../../lib/hearts/service/mapper'
 types = require '../../../lib/hearts/service/types/hearts_types'
-Hearts = require '../../../lib/hearts/service/types/hearts'
+Hearts = require '../../../lib/hearts/service/types/Hearts'
 Factory = require '../../factory'
 should = require("should")


Use which(1), not bash's type function

Use which(1), not bash's type function

--- spec/integration/startup-spec.coffee
+++ spec/integration/startup-spec.coffee
@@ -12,7 +12,7 @@
 ChromeDriverPort = 9515

 describe "Starting Atom", ->
-  if spawnSync("type", ["-P", "chromedriver"]).status isnt 0
+  if spawnSync("which", ["chromedriver"]).status isnt 0
     console.log "Skipping integration tests because the `chromedriver` executable was not found."
     return


Loader: Load ob.center.

Loader: Load ob.center.

--- engine/loader.coffee
+++ engine/loader.coffee
@@ -124,6 +124,7 @@
                 ob.packed_file = data.packed_file
                 ob.avg_poly_area = data.avg_poly_area or 10
                 ob.avg_poly_length = Math.pow(ob.avg_poly_area,0.5)
+                vec3.copy ob.center, data.center or [0,0,0]

         load_mesh_properties ob, data


revert back to panoptes server host

revert back to panoptes server host

oauth routes not working via www.zooniverse.org, will need to investigate and fix.

--- app/api/config.coffee
+++ app/api/config.coffee
@@ -1,7 +1,7 @@
 DEFAULT_ENV = 'staging'

 API_HOSTS =
-  production: 'https://www.zooniverse.org'
+  production: 'https://panoptes.zooniverse.org'
   staging: 'https://panoptes-staging.zooniverse.org'

 API_APPLICATION_IDS =


Adding tab length 2 to coffee script.

Adding tab length 2 to coffee script.

--- env/atom.config.cson
+++ env/atom.config.cson
@@ -1,4 +1,8 @@
 {
+  ".coffee.source":
+    editor:
+      tabLength: 2
+
   ".erb.html.text":
     editor:
       tabLength: 2
@@ -44,7 +48,6 @@

     editor:
       autoIndentOnPaste: false
-      invisibles: {}
       normalizeIndentOnPaste: false
       showIndentGuide: true
       showInvisibles: true


Improves opponent and winning message

Improves opponent and winning message

--- app/client/game/result.coffee
+++ app/client/game/result.coffee
@@ -2,18 +2,11 @@

 # methods

+# get the current player's role
 currentPlayerRole = ->
-  isChallenger = currentPlayerId() is currentChallenge().challengerId
-  isChallengee = currentPlayerId() is currentChallenge().challengeeId
-
-  # check for error
-  if isChallenger and isChallengee
-    throw new Meteor.Error 500, 'Cannot be challenger and challengee'
-
-  # return the players role
-  if isChallenger
+  if currentPlayerId() is currentChallenge().challengerId
     'challenger'
-  else if isChallengee
+  else
     'challengee'

@@ -31,7 +24,11 @@

 Template.challenge.helpers
   opponent: ->
-    opponent = Meteor.users.findOne currentChallenge().challengeeId
+    opponent = null
+    if currentPlayerRole() is 'challengee'
+      opponent = Meteor.users.findOne currentChallenge().challengerId
+    else
+      opponent = Meteor.users.findOne currentChallenge().challengeeId
     opponent.username

   answered: ->
@@ -72,7 +69,7 @@
       winner = 'tie'

     if winner is 'tie'
-      "Wow, du bundet!"
+      "Wow, der ble uafgjort!"
     else if currentPlayerRole() is winner
       "Tillykke, du har vundet!"
     else


allow latexmk to pass through options

allow latexmk to pass through options

this avoids problems in the latest version of latexmk where the
$pdflatex variable has been replaced by $xelatex and $lualatex when
running with -xelatex or -lualatex

--- app/coffee/LatexRunner.coffee
+++ app/coffee/LatexRunner.coffee
@@ -62,31 +62,32 @@
 		else
 			CommandRunner.kill ProcessTable[id], callback

-	_latexmkBaseCommand: (Settings?.clsi?.latexmkCommandPrefix || []).concat(
-			["latexmk", "-cd", "-f", "-jobname=output", "-auxdir=$COMPILE_DIR", "-outdir=$COMPILE_DIR"]
-		)
+	_latexmkBaseCommand: (Settings?.clsi?.latexmkCommandPrefix || []).concat([
+		"latexmk", "-cd", "-f", "-jobname=output", "-auxdir=$COMPILE_DIR", "-outdir=$COMPILE_DIR",
+		"-synctex=1","-interaction=batchmode"
+		])

 	_pdflatexCommand: (mainFile) ->
 		LatexRunner._latexmkBaseCommand.concat [
-			"-pdf", "-e", "$pdflatex='pdflatex -synctex=1 -interaction=batchmode %O %S'",
+			"-pdf",
 			Path.join("$COMPILE_DIR", mainFile)
 		]

 	_latexCommand: (mainFile) ->
 		LatexRunner._latexmkBaseCommand.concat [
-			"-pdfdvi", "-e", "$latex='latex -synctex=1 -interaction=batchmode %O %S'",
+			"-pdfdvi",
 			Path.join("$COMPILE_DIR", mainFile)
 		]

 	_xelatexCommand: (mainFile) ->
 		LatexRunner._latexmkBaseCommand.concat [
-			"-xelatex", "-e", "$pdflatex='xelatex -synctex=1 -interaction=batchmode %O %S'",
+			"-xelatex",
 			Path.join("$COMPILE_DIR", mainFile)
 		]

 	_lualatexCommand: (mainFile) ->
 		LatexRunner._latexmkBaseCommand.concat [
-			"-pdf", "-e", "$pdflatex='lualatex -synctex=1 -interaction=batchmode %O %S'",
+			"-lualatex",
 			Path.join("$COMPILE_DIR", mainFile)
 		]


speed up a test

speed up a test

--- app/coffee/tests/tests.coffee
+++ app/coffee/tests/tests.coffee
@@ -197,15 +197,15 @@
   ok isPointBlack($point), 'first player should be Black despite SGF info node'

 test 'clicking multiple points updates hidden form', ->
-  setInputSgf '(;)'
+  setInputSgf '(;SZ[3])'
   tesuji_charm.game_basic.initialize()
   $point1 = $pointAt 0, 0
   $point2 = $pointAt 1, 2

   $point1.click()
-  equal getResponseSgf(), '(;B[aa])', "first stone sets correct SGF"
+  equal getResponseSgf(), '(;SZ[3];B[aa])', "first stone sets correct SGF"
   $point2.click()
-  equal getResponseSgf(), '(;B[bc])', "second stone sets correct SGF"
+  equal getResponseSgf(), '(;SZ[3];B[bc])', "second stone sets correct SGF"

 test 'Confirm button disabled until stone placed', ->
   $button = $('button.confirm_button')


fix stepper bug

fix stepper bug

--- app/components/stepper.coffee
+++ app/components/stepper.coffee
@@ -29,7 +29,7 @@

       if @_max? and (val|0) > @_max
         val = @_max
-      else if @_min and (val|0) < @_min
+      else if @_min? and (val|0) < @_min
         val = @_min

       super @_prepare_value(val)


Sort overlays.

Sort overlays.

--- app/javascripts/router.coffee
+++ app/javascripts/router.coffee
@@ -148,8 +148,18 @@
             # described in the extendModeling and extendSession
             # functions below.
             extendSessions = ->
-              # Adds the following modeling parameter result
-              # properties:
+              # Adds the following modeling label map properties:
+              # * parameterResult - the parent parameter result object
+              # * key - the parent parameterResult key
+              #
+              # @param labelMap the object to extend
+              # @param paramResult the parent parameter result
+              extendLabelMap = (labelMap, paramResult) ->
+                labelMap.parameterResult = paramResult
+                Object.defineProperty labelMap, 'key',
+                  get: ->
+                    @parameterResult.key
+              # Adds the following modeling parameter result properties:
               # * key - the parameter result access property name
               # * modelingResult - the parent modeling result
               #   reference
@@ -205,16 +215,22 @@
                 # Extend the modeling result object.
                 extendModelingResult(modeling.result, modeling)
                 # The modeling overlays property returns the label maps
-                # which have a color table.
+                # which have a color table. The overlays array is sorted
+                # in modeling parameter name order.
                 Object.defineProperty modeling, 'overlays',
                   get: ->
-                    (res.overlay for res in _.values(@result) when res.overlay?)
+                    overlayed = (
+                      res for res in _.values(@result) when res.overlay?
+                    )
+                    sorted = _.sortBy(overlayed, 'key')
+                    _.pluck(sorted, 'overlay')

               # Fixes the session acquisition date and adds the following
               # session properties:
               # * number - the one-based session number in acquisition order
               # * subject - the session parent subject reference
-              # * overlays - the label map objects with a color table
+              # * overlays - the {modeling protocol id: [label map objects]}
+              #   for those label maps which have a color table
               extendSession = (session, number) ->
                 # Set the session subject property.
                 session.subject = subject
@@ -225,11 +241,14 @@
                 # Add the modeling properties.
                 for modeling in session.modelings
                   extendModeling(modeling, session)
-                # The session overlays property returns the label map
-                # objects which have a color table.
+                # The session overlays property.
                 Object.defineProperty session, 'overlays',
                   get: ->
-                    _.flatten(_.pluck(@modelings, 'overlays'))
+                    associate = (accum, modeling) ->
+                      accum[modeling.protocol] = modeling.overlays
+                    modelings = (mdl for mdl in @modelings when mdl.overlays.length?)
+                    if modelings.length?
+                      _.reduce(modelings, associate, {})

               # Extend each session.
               for session, i in subject.sessions
@@ -362,33 +381,38 @@
           # * scan - the registration source scan
           #
           # @param registration the registration to extend
+          # @param number the one-based registration index within the scan
           # @param scan the source scan
-          extendRegistration = (registration, scan) ->
+          extendRegistration = (registration, number, scan) ->
             # Set the source scan reference.
             registration.scan = scan
+            # Format the title.
+            registration.title = "Scan #{ scan.number } Registration" +
+                                 " #{ registration.number }"
             # Add the registration volume properties.
             extendImageContainer(registration)

           # * Extends the scan registrations as described in
           #   extendRegistration
           # * Converts the scan number to an integer
-          # * Extends the scan object as described
-          #   in extendImageContainer
+          # * Extends the scan object as described in extendImageContainer
           # * Adds the following properties:
           #   * session - the source scan parent session
           #
           # @param scan the scan to extend
           extendScan = (scan) ->
-            # The number is read as an integer, as with all JSON values.
+            # The number is read as a string, as with all JSON values.
             # Convert it to an integer.
             scan.number = parseInt(scan.number)
+            # The scan title.
+            scan.title = "Scan #{ scan.number }"
             # Set the session reference.
             scan.session = session
             # Add the scan volume properties.
             extendImageContainer(scan)
             # Add the scan registration properties.
-            for reg in scan.registrations
-              extendRegistration(reg, scan)
+            for reg, regNbr in scan.registrations
+              extendRegistration(reg, regNbr + 1, scan)

           # The session object must have a detail reference.
           if not session.detail?
@@ -416,8 +440,6 @@
                         (scan) -> scan.number is scanNumber)
           # If no such scan, then complain.
           if not scan?
-            for scan in session.scans
-              console.log(">> #{ scan.number } vs #{ scanNumber }")
             throw new ReferenceError "#{ subjectTitle(session) } does not have" +
                                      " a scan with number #{ scanNumber }"
           # Return the scan.