Remove local Component class. Refactor.

Remove local Component class. Refactor.

--- table.coffee
+++ table.coffee
@@ -1,42 +1,29 @@
+class $blab.Table extends $blab.Component
+
+    constructor: (@spec, sheet, file) ->

-class Component
+        @sheet = sheet[@spec.id]

-    constructor: ->
+        container = $("##{@spec.containerId}")
+        container.append("

")
+
+        hot = $("#Table-#{@spec.id}")
+        hot.css("position", "absolute")
+        hot.css("left", @spec.x)
+        hot.css("top", @spec.y)
+
+        defaults =
+            data: @sheet.data
+            afterChange: (change, source) =>
+                $blab.compute() if source is "edit" and @spec.compute
+            columns: ({type: 'numeric'} for k in [1..@sheet.data[0].length])
+            rowHeaders: @sheet.rowHeaders
+            colHeaders: @sheet.colHeaders
+            contextMenu: false
+
+        @table = new Handsontable hot[0], $.extend({}, defaults, @spec)

     update: ->
-
-    stringify: ->
-        JSON.stringify(@spec)
-
-class $blab.Table extends Component
-
-    constructor: (@spec, sheet) ->
-
-        randPos = (range, offset)->
-            "#{Math.round((Math.random()*range+offset)*100)}%"
-        @spec.x ?= randPos(0.8, 0.1)
-        @spec.y ?= randPos(0.8, 0.1)
-
-        @sheet = sheet[@spec.id]
-
-        container = $("##{@spec.id}")
-        container.append("

")
-        container.css("position", "absolute")
-        container.css("left", @spec.x)
-        container.css("top", @spec.y)
-
-        hot = $("##{@spec.id} .hot")
-        @defaults =
-            data: @sheet.spec.data
-            afterChange: (change, source) =>
-                $blab.compute() if source is "edit" and @sheet.spec.compute
-            columns: ({type: 'numeric'} for k in [1..@sheet.spec.data[0].length])
-            rowHeaders: @sheet.spec.rowHeaders
-            colHeaders: @sheet.spec.colHeaders
-            contextMenu: false
-        @table = new Handsontable hot[0], $.extend({}, @defaults, @spec)
-
-    update: ->
-        @table.loadData @sheet.spec.data
+        @table.loadData @sheet.data
         @table.render()


admin teacher cannot access licenses page (#5926)

Co-authored-by: smallst_wsl <6c42689ff77371401bf43b7d56b93ac1ca3e92d3@163.com>

--- app/views/courses/EnrollmentsView.coco.coffee
+++ app/views/courses/EnrollmentsView.coco.coffee
@@ -74,7 +74,7 @@
       @newAdministeredClassrooms = new Classrooms()
       @allAdministeredClassrooms = []
       @listenTo @newAdministeredClassrooms, 'sync', @newAdministeredClassroomsSync
-      teachers = me.get('administratedTeachers')
+      teachers = me.get('administratedTeachers') ? []
       @totalAdministeredTeachers = teachers.length
       teachers.forEach((teacher) =>
         @supermodel.trackRequest @newAdministeredClassrooms.fetchByOwner(teacher)


player: catch exception in play promise

player: catch exception in play promise

--- src/javascripts/player.coffee
+++ src/javascripts/player.coffee
@@ -171,7 +171,7 @@

         $(@media).on('loadedmetadata', setTime)

-    @media.play().then(() => @setMediaSessionInfo())
+    @media.play().then(() => @setMediaSessionInfo()).catch((e) => console.debug(e))
     @playing = true
     @app.togglePlayState()


Specify port for the test api, and give a callback to log connection to console

--- test/api.coffee
+++ test/api.coffee
@@ -13,9 +13,10 @@

   it 'should start and provide a WS server', ->
     api = new Api
-    api.start =>
+    api.start {port: 3022, host: "0.0.0.0"}, () =>
+      console.log 'called api callback'

-    client = new WebSocket("ws://localhost:3001")
+    client = new WebSocket("ws://localhost:3022")
     client.on 'connection', () ->
       client.send '{"itsjson":"morejson"}'


Use Remark beautifier for markdown files by default

--- src/languages/markdown.coffee
+++ src/languages/markdown.coffee
@@ -18,7 +18,7 @@
     "md"
   ]

-  defaultBeautifier: "Tidy Markdown"
+  defaultBeautifier: "Remark"

   options:
     gfm:


Fix ordering on workers lists

Fix ordering on workers lists

Emebr does not allow to use mixin on create, it needs to be passed to
extend().

(closes #151)

--- assets/scripts/app/controllers/sidebar.coffee
+++ assets/scripts/app/controllers/sidebar.coffee
@@ -33,11 +33,11 @@
         for worker in content.toArray()
           host = worker.get('host')
           unless groups[host]
-            groups[host] = Em.ArrayProxy.create(Em.SortableMixin,
+            groups[host] = Em.ArrayProxy.extend(Em.SortableMixin,
               content: [],
               sortProperties: ['nameForSort']
-            )
-          groups[host].addObject(worker)
+            ).create()
+          groups[host].pushObject(worker)

         $.values(groups)
     ).property('length')


Edit main navigation text

Edit main navigation text

--- app/partials/main-header.cjsx
+++ app/partials/main-header.cjsx
@@ -12,8 +12,8 @@
 counterpart.registerTranslations 'en',
   mainNav:
     home: 'Home'
-    discover: 'Discover'
-    learn: 'Learn'
+    projects: 'Projects'
+    about: 'About'
     collect: 'Collect'
     talk: 'Talk'
     lab: 'Build a project'
@@ -63,8 +63,8 @@
           <ZooniverseLogo />
         </Link>
         <nav className="main-nav">
-          <Link to="projects" className="main-nav-item"><Translate content="mainNav.discover" /></Link>
-          <Link to="about" className="main-nav-item"><Translate content="mainNav.learn" /></Link>
+          <Link to="projects" className="main-nav-item"><Translate content="mainNav.projects" /></Link>
+          <Link to="about" className="main-nav-item"><Translate content="mainNav.about" /></Link>
           <Link to="talk" className="main-nav-item"><Translate content="mainNav.talk" /></Link>
           <Link to="collections" className="main-nav-item"><Translate content="mainNav.collect" /></Link>
           <hr />


clearn out pending specs if specs haven't changed

--- test/config/test-runner.coffee
+++ test/config/test-runner.coffee
@@ -19,7 +19,10 @@
     specs = _.unique @pendingSpecs

     # no need to start a server if there are no pending specs, or the spec list hasn't changed
-    return if _.isEmpty(@pendingSpecs) or _.isEqual(@curSpecs?.sort(), specs.sort())
+    return if _.isEmpty(@pendingSpecs)
+    if _.isEqual(@curSpecs?.sort(), specs.sort())
+      @pendingSpecs = []
+      return

     # if there's already a karma instance running, then kill it, since we're starting a new one
     if @isKarmaRunning and @child


refactor charts.coffee

refactor charts.coffee

--- app/assets/javascripts/app/chart.coffee
+++ app/assets/javascripts/app/chart.coffee
@@ -20,7 +20,7 @@
         ]
     series

-  generateMonthCategories: (labels) ->
+  generateCategories: (labels) ->
     categories = []
     for month, i in labels
       date = moment(month)
@@ -37,16 +37,15 @@
         break
     currentWeek

-  invoicesChart: (id, data) ->
+  baseChartOptions: (id, data) ->
     currentWeek = @getCurrentWeek(data.labels)
-    $(id).highcharts
+    {
       chart:
         type: 'line'
       credits:
         enabled: false
       title:
         text: null
-        useHTML: true
       noData:
         style:
           fontSize: "18px"
@@ -59,30 +58,26 @@
               mouseOver: ->
                 $("#{id} .highcharts-xaxis-labels span").removeClass('hover')
                 $("#{id} .highcharts-xaxis-labels span:contains(#{@category.short})").addClass('hover')
-      xAxis: [{
-        categories: @generateMonthCategories(data.labels),
-        crosshair: true,
+      xAxis:
+        categories: @generateCategories(data.labels)
         title:
           text: null
         labels:
           format: '{value.short}'
           useHTML: true
         plotBands: [{
-          color: 'rgba(66, 139, 202, 0.2)'
+          color: 'rgba(155, 200, 255, 0.2)'
           from: currentWeek.start
           to: currentWeek.end
         }]
-      }],
       yAxis:
         startOnTick: false
-        labels:
-          format: '{value}k €'
-          formatter: ->
-            value = if @value < 1000 then @value else "#{@value / 1000.0}k"
-            "#{value} €"
         title:
           text: null
       tooltip:
+        crosshairs: [{
+          color: 'rgba(200, 200, 200, 0.2)'
+        }]
         shared: true
         headerFormat: '
{point.key.long}
'
         pointFormatter: ->
@@ -94,110 +89,66 @@
           enabled: false
       legend:
         enabled: false
-        useHTML: true
-        verticalAlign: 'top'
       series: @generateSeries(data.datasets)
+    }
+
+  invoicesChart: (id, data) ->
+    options = @baseChartOptions(id, data)
+    options.yAxis['labels'] =
+      format: '{value}k €'
+      formatter: ->
+        value = if @value < 1000 then @value else "#{@value / 1000.0}k"
+        "#{value} €"
+
+    $(id).highcharts(options)

   budgetChart: (id, data) ->
-    currentWeek = @getCurrentWeek(data.labels)
-    $(id).highcharts
-      chart:
-        type: 'line'
-        events:
-          load: (e) ->
-            return unless data.datasets[0]
-            segments = []
-            width = @plotWidth / @pointCount
-            lastPosition = -1
-            for position in @xAxis[0].tickPositions
-              segmentPosition = @plotLeft
-              segmentWidth = 0
-              for i in [0..@pointCount - 1]
-                if i <= position
-                  segmentPosition += width
-                  if i > lastPosition
-                    segmentWidth += width
+    options = @baseChartOptions(id, data)
+    options.chart['events'] =
+      load: (e) ->
+        return unless data.datasets[0]
+        segments = []
+        width = @plotWidth / @pointCount
+        lastPosition = -1
+        for position in @xAxis[0].tickPositions
+          segmentPosition = @plotLeft
+          segmentWidth = 0
+          for i in [0..@pointCount - 1]
+            if i <= position
+              segmentPosition += width
+              if i > lastPosition
+                segmentWidth += width

-              segments.push
-                position: segmentPosition
-                width: segmentWidth
+          segments.push
+            position: segmentPosition
+            width: segmentWidth

-              lastPosition = position
+          lastPosition = position

-            for segment, i in segments
-              $label = $($(id).find('.highcharts-xaxis-labels span')[i])
-              $label.css('left', segment.position - (segment.width / 2) - ($label.width() / 2) - 1)
+        for segment, i in segments
+          $label = $($(id).find('.highcharts-xaxis-labels span')[i])
+          $label.css('left', segment.position - (segment.width / 2) - ($label.width() / 2) - 1)
+    options.xAxis['tickPositions'] = data.ticks
+    options.yAxis['min'] = if data.datasets[0] && parseInt(data.datasets[0].data[0], 10) is 0 then undefined else 0
+    options.yAxis.startOnTick = false
+    options.yAxis['labels'] =
+      useHTML: true
+      format: '{value}k €'
+      formatter: ->
+        value = if @value < 1000 then @value else "#{@value / 1000.0}k"
+        "#{value} €"
+    options.yAxis['plotLines'] = [{
+      value: data.budget
+      color: '#777777'
+      width: 2
+      label:
+        useHTML: true
+        style:
+          fontSize: null
+        text: "#{I18n.t("labels.chart.project.budget_estimate")}: #{accounting.formatMoney(data.budget, {symbol: '€', format: '%v %s', decimal: ',', thousand: '.'})}"
+    }]

-      credits:
-        enabled: false
-      title:
-        text: null
-        useHTML: true
-      noData:
-        style:
-          fontSize: "18px"
-          fontWeight: "bold"
-          color: "#ccc"
-      plotOptions:
-        series:
-          point:
-            events:
-              mouseOver: ->
-                $("#{id} .highcharts-xaxis-labels span").removeClass('hover')
-                $("#{id} .highcharts-xaxis-labels span:contains(#{@category.short})").addClass('hover')
-      xAxis: [{
-        categories: @generateMonthCategories(data.labels),
-        title:
-          text: null
-        labels:
-          format: '{value.short}'
-          useHTML: true
-        plotBands: [{
-          color: 'rgba(66, 139, 202, 0.2)'
-          from: currentWeek.start
-          to: currentWeek.end
-        }]
-        tickPositions: data.ticks
-      }],
-      yAxis:
-        min: if data.datasets[0] && parseInt(data.datasets[0].data[0], 10) is 0 then undefined else 0
-        startOnTick: false
-        labels:
-          useHTML: true
-          format: '{value}k €'
-          formatter: ->
-            value = if @value < 1000 then @value else "#{@value / 1000.0}k"
-            "#{value} €"
-        title:
-          text: null
-        plotLines: [{
-          value: data.budget
-          color: '#777777'
-          width: 2
-          label:
-            useHTML: true
-            style:
-              fontSize: null
-            text: "#{I18n.t("labels.chart.project.budget_estimate")}: #{accounting.formatMoney(data.budget, {symbol: '€', format: '%v %s', decimal: ',', thousand: '.'})}"
-        }]
-      tooltip:
-        shared: true
-        crosshairs: [{
-          color: 'rgba(51, 51, 51, 0.2)'
-        }]
-        headerFormat: '
{point.key.date}
'
-        pointFormatter: ->
-          value = accounting.formatMoney(@y, {symbol: '€', format: '%v %s', decimal: ',', thousand: '.'})
-          "

\u25CF #{@series.name}:

#{value}

"
-        useHTML: true
-      navigation:
-        buttonOptions:
-          enabled: false
-      legend:
-        enabled: false
-        useHTML: true
-        verticalAlign: 'top'
-      series: @generateSeries(data.datasets)
+    $(id).highcharts(options)

 $(document).on 'mouseleave', '.chart', ->
   $(@).find('.highcharts-xaxis-labels span').removeClass('hover')


preserve current query params when removing a label

--- app/assets/javascripts/issues.js.coffee
+++ app/assets/javascripts/issues.js.coffee
@@ -52,6 +52,19 @@

   $( "#labels-legend" ).hide()

+  # http://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript
+  urlParams = null
+  (window.onpopstate = ->
+    pl     = /\+/g # Regex for replacing addition symbol with a space
+    search = /([^&=]+)=?([^&]*)/g
+    decode = (s) -> return decodeURIComponent(s.replace(pl, " "))
+    query  = window.location.search.substring(1)
+
+    urlParams = {}
+    while (match = search.exec(query))
+      urlParams[decode(match[1])] = decode(match[2])
+  )()
+
   # remove repo label prefilters:
   $(".remove-label-button").on "click", (ev) ->
     $button = $(ev.currentTarget).parents(".label-badge").remove()
@@ -62,4 +75,6 @@

     labels = labels.join(",")

-    window.location.search = "?labels=#{labels}"
+    urlParams["labels"] = labels
+
+    window.location.search = "?" + $.param(urlParams)


fastclickが組み込まれていなかったのを修正

fastclickが組み込まれていなかったのを修正

--- app/assets/javascripts/viewer.js.coffee
+++ app/assets/javascripts/viewer.js.coffee
@@ -2082,4 +2082,6 @@

     @

-$ -> (new Viewer())
+$ ->
+  FastClick.attach(document.body)
+  new Viewer()


Put a 2-second delay before websocket connecting

--- app/assets/javascripts/ws.helper.coffee
+++ app/assets/javascripts/ws.helper.coffee
@@ -13,7 +13,7 @@
         constructor: (url) ->
           @url = url
           @handlers = {}
-          @connect() if options.autoConnect ? true
+          $timeout @connect, 2000 if options.autoConnect ? true

         register: (handlers...) ->
           @handlers[handler.protocol] = handler for handler in handlers


Add trackers to links to other projects for #73

--- app/translations/en_us.coffee
+++ app/translations/en_us.coffee
@@ -19,9 +19,9 @@

With your help, we've classified all the data we have so far. Great work!

         We're leaving some images up for further classification, but we have more projects
-        (like Cyclone Center
-        and Bat Detective) that need your help.
-        Visit zooniverse.org to see them all.
+        (like Cyclone Center
+        and Bat Detective) that need your help.
+        Visit zooniverse.org to see them all.

         Keep an eye on the blog


Move shouldDestroyOnFileDelete callback to constructor

--- src/text-buffer.coffee
+++ src/text-buffer.coffee
@@ -50,6 +50,9 @@
   #   * `load` A {Boolean}, `true` to asynchronously load the buffer from disk
   #     after initialization.
   #   * `text` The initial {String} text of the buffer.
+  #   * `shouldDestroyOnFileDelete` A {Function} that returns a {Boolean}
+  #     indicating whether the buffer should be destroyed if its file is
+  #     deleted.
   constructor: (params) ->
     text = params if typeof params is 'string'

@@ -79,7 +82,7 @@
     @transactCallDepth = 0
     @digestWhenLastPersisted = params?.digestWhenLastPersisted ? false

-    @shouldDestroyBufferOnFileDelete = -> false
+    @shouldDestroyOnFileDelete = params?.shouldDestroyOnFileDelete ? -> false

     @setPath(params.filePath) if params?.filePath
     @load() if params?.load
@@ -132,9 +135,6 @@
     digestWhenLastPersisted: @file?.getDigestSync()
     preferredLineEnding: @preferredLineEnding
     nextMarkerId: @nextMarkerId
-
-  setConfigCallbacks: (shouldDestroyBufferOnFileDelete) ->
-    @shouldDestroyBufferOnFileDelete = shouldDestroyBufferOnFileDelete

   ###
   Section: Event Subscription
@@ -1477,7 +1477,7 @@
       if modified
         @updateCachedDiskContents()
       else
-        if @shouldDestroyBufferOnFileDelete()
+        if @shouldDestroyOnFileDelete()
           @destroy()
         else
           @unsubscribeFromFile()


:pencil: checkpoint methods, “experimental” for now.

--- src/text-buffer.coffee
+++ src/text-buffer.coffee
@@ -840,9 +840,30 @@
   # Public: Clear the undo stack.
   clearUndoStack: -> @history.clearUndoStack()

+  # Experimental: Create a pointer to the current state of the buffer for use
+  # with {::revertToCheckpoint} and {::groupChangesSinceCheckpoint}.
+  #
+  # Returns a checkpoint value.
   createCheckpoint: -> @history.createCheckpoint()

+  # Experimental: Revert the buffer to the state it was in when the given
+  # checkpoint was created.
+  #
+  # The redo stack will be empty following this operation, so changes since the
+  # checkpoint will be lost. If the given checkpoint is no longer present in the
+  # undo history, no changes will be made to the buffer and this method will
+  # return `false`.
+  #
+  # Returns a {Boolean} indicating whether the operation succeeded.
   revertToCheckpoint: (checkpoint) -> @history.revertToCheckpoint(checkpoint)
+
+  # Experimental: Group all changes since the given checkpoint into a single
+  # transaction for purposes of undo/redo.
+  #
+  # If the given checkpoint is no longer present in the undo history, no
+  # grouping will be performed and this method will return `false`.
+  #
+  # Returns a {Boolean} indicating whether the operation succeeded.
   groupChangesSinceCheckpoint: (checkpoint) -> @history.groupChangesSinceCheckpoint(checkpoint)

   ###


Guard against null params

Guard against null params

--- src/text-buffer.coffee
+++ src/text-buffer.coffee
@@ -61,7 +61,7 @@
     @setTextInRange([[0, 0], [0, 0]], text ? params?.text ? '', false)
     @history = params?.history ? new History(this)
     @markers = params?.markers ? new MarkerManager(this)
-    @setEncoding(params.encoding)
+    @setEncoding(params?.encoding)

     @loaded = false
     @digestWhenLastPersisted = params?.digestWhenLastPersisted ? false


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com


class include module测试onInclude被如何调用断言更准确

--- test/baseSpec.coffee
+++ test/baseSpec.coffee
@@ -126,8 +126,8 @@
             A.prototype.should.have.property("p1", m1.p1)
             A.prototype.should.have.property("p2", m2.p2)

-            new A()
-            m1.onIncluded.called.should.be.true
+            a = new A()
+            m1.onIncluded.calledWith(a).should.be.true


include Cooldown as default module

include Cooldown as default module

--- test/cooldown.coffee
+++ test/cooldown.coffee
@@ -25,8 +25,6 @@
       powerup:
         target: 0
         approachBy: 1
-
-  obj.include(Cooldown)

   11.times ->
     obj.update()


Reset gruntError after passing it to callback

--- test/node_gyp.coffee
+++ test/node_gyp.coffee
@@ -23,6 +23,7 @@
 execGruntTask = (task, callback) ->
 	grunt.tasks 'gyp:' + task, gruntOptions, ->
 		callback(gruntError)
+		gruntError = null

 describe 'grunt-node-gyp', ->
 	describe 'configure', ->


Uses v2 tags and registry

Uses v2 tags and registry

--- src/version-map.coffee
+++ src/version-map.coffee
@@ -10,9 +10,9 @@
     @bucket = options.bucket or 'vtex-versioned'
     @dryRun = options.dryRun
     console.log '\nWARNING: VersionMap running in dry run mode. No changes will actually be made.\n' if @dryRun
-    @registryPath = "registry/1/registry.json"
+    @registryPath = "registry/v2/registry.json"
     @tagsPath = "registry/1/tags.json"
-    @newTagsPath = "tags/1/tags.json" #TODO replace tagsPath once Janus uses new version
+    @newTagsPath = "tags/v2/tags.json" #TODO replace tagsPath once Janus uses new version

   # Updates the registry with the pkg package informations
   # Package has two required properties: name and version
@@ -145,4 +145,4 @@
   versionDirectory: (packageObj) =>
     packageObj.name + "/" +  packageObj.version

-module.exports = VersionMap+module.exports = VersionMap


fix vertical command parsing

fix vertical command parsing

--- src/path-parser.coffee
+++ src/path-parser.coffee
@@ -65,7 +65,9 @@
         firstNode = moveNode if moveNode

         params = command.parameters
-        params = makeAbsolute(params) if command.type == 'v'
+        if command.type == 'v'
+          params = makeAbsolute([0, params[0]])
+          params = params.slice(1)

         currentPoint = [currentPoint[0], params[0]]
         result.nodes.push(new Curve.Node(currentPoint))


fixed path

fixed path

--- Tests/spec/dialog.spec.coffee
+++ Tests/spec/dialog.spec.coffee
@@ -1,4 +1,4 @@
-require "../../../JsTestBundle/Resources/config/init.coffee"
+require "./init.coffee"
 # file to test
 require "../../Resources/coffee/dialog.coffee"


make password reset from terminal open only one connection to db

--- src/smc-hub/hub.coffee
+++ src/smc-hub/hub.coffee
@@ -2349,7 +2349,9 @@
     account_id = undefined
     async.series([
         (cb) ->
-            connect_to_database(cb:cb)
+            connect_to_database
+                pool : 1
+                cb   : cb
         (cb) ->
             database.get_account
                 email_address : email_address


atom-markdown-preview: init config

atom-markdown-preview: init config

--- .atom/config.cson
+++ .atom/config.cson
@@ -34,6 +34,8 @@
   "linter-pep257": {}
   "linter-pep8": {}
   "linter-python-pep257": {}
+  "markdown-preview":
+    useGitHubStyle: true
   "tree-view":
     autoReveal: true
     focusOnReveal: false


save config

save config

--- .atom/config.cson
+++ .atom/config.cson
@@ -39,6 +39,7 @@
   "markdown-preview":
     useGitHubStyle: true
   "markdown-table-formatter": {}
+  "one-dark-ui": {}
   "spell-check": {}
   "status-bar": {}
   tabs:


update atom config

update atom config

--- .atom/config.cson
+++ .atom/config.cson
@@ -35,11 +35,7 @@
   "git-diff": {}
   "line-ending-converter":
     showEolInStatusBar: false
-  linter:
-    showErrorPanel: false
-    showErrorTabFile: false
-    showErrorTabProject: false
-    statusIconScope: "File"
+  linter: {}
   "linter-alex": {}
   "linter-rubocop": {}
   "linter-ruby": {}
@@ -47,10 +43,15 @@
     enableNotice: true
     shellcheckExecutablePath: "/usr/local/bin/shellcheck"
     userParameters: "-x"
+  "linter-ui-default": {}
   "linter-write-good": {}
   "markdown-preview":
     useGitHubStyle: true
-  "markdown-table-formatter": {}
+  "markdown-table-formatter":
+    markdownGrammarScopes: [
+      "source.gfm"
+      "text.plain.null-grammar"
+    ]
   minimap:
     plugins:
       "find-and-replace": true


Fix typo

Fix typo

--- common.coffee
+++ common.coffee
@@ -32,7 +32,7 @@
     attempts++
     runJs javascript, (e) ->
       if e and attempts < 3
-        console.error("Running JS failed; trying again (#{attempt+1}/3)")
+        console.error("Running JS failed; trying again (#{attempts+1}/3)")
         setTimeout(doIt, attempts * attempts * 250)
       else
         callback.apply this, arguments


Use the correct script name in examples

Use the correct script name in examples

--- summon.coffee
+++ summon.coffee
@@ -11,8 +11,8 @@
 # Make sure you gave us a package to install, otherwise exit with an error
 if process.argv.length < 3
   console.log 'Pass the package name you want to install as the first argument.'
-  console.log 'Usage: ./install [packageName]'
-  console.log 'Example: ./install git'
+  console.log 'Usage: ./summon [packageName]'
+  console.log 'Example: ./summon git'
   process.exit 1

 # This is the thing we want to install


Log router transitions

Log router transitions

--- assets/scripts/app/app.coffee
+++ assets/scripts/app/app.coffee
@@ -1,5 +1,6 @@
 unless window.TravisApplication
   window.TravisApplication = Em.Application.extend(Ember.Evented,
+    LOG_TRANSITIONS: true
     authStateBinding: 'auth.state'
     signedIn: (-> @get('authState') == 'signed-in' ).property('authState')


Made export the two imported modules. Just a convenience wrapper.

--- v1sdk.coffee
+++ v1sdk.coffee
@@ -1,2 +1,3 @@
-V1Meta = require('./v1meta').V1Meta
-V1Server = require('./client').V1Server+module.exports =
+	V1Meta: require('./v1meta').V1Meta
+	V1Server: require('./client').V1Server


Don't override the first history item.

Don't override the first history item.

--- js/flight.coffee
+++ js/flight.coffee
@@ -64,7 +64,7 @@
     if @pageHistory.length > 1 and window.location.hash is @pageHistory[@pageHistory.length - 2]
       @back = true

-    if @back is true
+    if @back is true and @pageHistory.length != 1
       transitionType = @currentPanel.getAttribute("data-transition")
       @pageHistory.pop()
     else


table pagination

table pagination

--- js/render.coffee
+++ js/render.coffee
@@ -17,6 +17,7 @@
     block: 1500
     media: 1000
     article: 0
+    table: 1500

    PAGE_MARGIN = '1000px'
    START = 1


Fixed graphics glitch on head reuse

Fixed graphics glitch on head reuse

--- jarkle/client/webgl/web_gl_visualisation.coffee
+++ jarkle/client/webgl/web_gl_visualisation.coffee
@@ -293,10 +293,10 @@
             obj.visible = true
         else if trailHead.updatedOn != update
           trailHead.position.z -= CUBE_DECREMENTS
-          trailHead.updatedOn = update
           if trailHead.position.z < -CUBE_DISTANCE_LIMIT
             trailHead.traverse (obj) ->
               obj.visible = false
+        trailHead.updatedOn = update
         headIndex = (headIndex + 1) % touches.heads.length

     for foxHead, foxHeadIndex in @foxHeads


payment: remove obsolete unused methods from `PaymentController`

--- client/Main/payment/paymentcontroller.coffee
+++ client/Main/payment/paymentcontroller.coffee
@@ -275,22 +275,3 @@
       subscription.plan = plansByCode[subscription.planCode]

     { plans, subscriptions }
-
-  debitWrapper: (options = {}, callback) ->
-    options.fn = @debitSubscription.bind this
-    @_runWrapper options, callback
-
-  creditWrapper: (options = {}, callback) ->
-    options.fn = @creditSubscription.bind this
-    @_runWrapper options, callback
-
-  _runWrapper: (options, callback) ->
-    {fn, subscriptionTag, packTag} = options
-
-    @fetchActiveSubscription (err, subscription) ->
-      return callback err  if err
-      KD.remote.api.JPaymentPack.one tags: packTag, (err, pack) =>
-        return callback err  if err
-        fn subscription, pack, (err, nonce) =>
-          return callback err  if err
-          callback null, nonce


application manager getAppByView fix for background apps

--- client/app/MainApp/ApplicationManager.coffee
+++ client/app/MainApp/ApplicationManager.coffee
@@ -28,14 +28,6 @@
     @frontApp = null

     @on 'AppManagerWantsToShowAnApp', @bound "setFrontApp"
-
-  getByView: (view)->
-    appInstance = null
-    for name, apps of @appControllers
-      apps.forEach (appController)=>
-        if view.getId() is appController.getView().getId()
-          appInstance = appController
-    return appInstance

   open: do ->

@@ -132,9 +124,17 @@
     @unregister appInstance
     callback()

-  get:(name)->
-
-    @appControllers[name]?.first or null
+  get:(name)-> @appControllers[name]?.first or null
+
+  getByView: (view)->
+
+    appInstance = null
+    for name, apps of @appControllers
+      apps.forEach (appController)=>
+        if view.getId() is appController.getView?().getId()
+          appInstance = appController
+
+    return appInstance

   getFrontApp:-> @frontApp


ApplicationManager: implemented argument support to appManager::open <name>, [options], [callback]

--- client/app/MainApp/ApplicationManager.coffee
+++ client/app/MainApp/ApplicationManager.coffee
@@ -45,7 +45,7 @@

   open: do ->

-    createOrShow = (appOptions, callback = noop)->
+    createOrShow = (appOptions, appParams, callback = noop)->

       name = appOptions?.name
       return warn "No such application!"  unless name
@@ -54,7 +54,7 @@
       appInstance = appManager.get name
       cb          = -> appManager.show appOptions, callback
       if appInstance then do cb
-      else appManager.create name, cb
+      else appManager.create name, appParams, cb

     (name, options, callback)->

@@ -65,7 +65,8 @@
       return warn "ApplicationManager::open called without an app name!"  unless name

       appOptions           = KD.getAppOptions name
-      defaultCallback      = -> createOrShow appOptions, callback
+      appParams            = options.params or {}
+      defaultCallback      = -> createOrShow appOptions, appParams, callback
       kodingAppsController = @getSingleton("kodingAppsController")

       # If app has a preCondition then first check condition in it
@@ -169,11 +170,14 @@
     if app then cb app
     else @create name, cb

-  create:(name, callback)->
-
-    AppClass   = KD.getAppClass name
-    appOptions = KD.getAppOptions name
-    @register appInstance = new AppClass($.extend {}, true, appOptions)  if AppClass
+  create:(name, params, callback)->
+
+    [callback, params] = [params, callback]  unless callback
+
+    AppClass              = KD.getAppClass name
+    appOptions            = $.extend {}, true, KD.getAppOptions name
+    appOptions.params     = params
+    @register appInstance = new AppClass appOptions  if AppClass
     @utils.defer -> callback? appInstance

   show:(appOptions, callback)->


Rephrasing help info

Rephrasing help info

--- twitch.coffee
+++ twitch.coffee
@@ -8,9 +8,10 @@
 #   None
 #
 # Commands:
-#   hubot ttv game <category> - Performs a case-sensitive search on Twitch.tv for live streams in a game <category> (returns the first 5)
-#   hubot ttv featured - Returns the first 5 featured live streams on Twitch.tv
+#   hubot ttv game <category> - Returns the first 5 live streams in a game <category> (case-sensitive)
+#   hubot ttv featured - Returns the first 5 featured live streams
 #   hubot ttv stream <name> - Returns information about stream <name>
+#   hubot ttv search <query> - Returns a list of live streams matching the search <query>
 #
 # Author:
 #   MrSaints


Change series to volume.

Change series to volume.

--- app/javascripts/router.coffee
+++ app/javascripts/router.coffee
@@ -1,5 +1,5 @@
-define ['angular', 'lodash', 'underscore.string', 'moment', 'rest', 'helpers',
-        'image', 'resources'],
+define ['angular', 'lodash', 'underscore.string', 'moment', 'rest',
+        'helpers', 'image', 'resources'],
   (ng, _, _s, moment, REST) ->
     router = ng.module 'qiprofile.router', ['qiprofile.resources',
                                             'qiprofile.helpers',
@@ -11,7 +11,10 @@
         # The Subject search fields.
         SUBJECT_SECONDARY_KEY_FIELDS = ['project', 'collection', 'number']

-        # @returns a collection/subject/session title string
+        # @param session the session object
+        # @returns the session subject title string in the format
+        #   *collection* Subject *number* Session *number*, e.g.
+        #   'Breast Subject 1 Session 2'
         subjectTitle = (session) ->
           "#{ session.subject.collection }" +
           " Subject #{ session.subject.number }" +
@@ -46,114 +49,82 @@
           # * adds parent references
           # * fixes dates
           # * adds the subject modeling property
+          #
+          # @param subject the subject object to augment
           extendSubject = (subject) ->
-            # # Extends the scan sets as follows:
-            # # * adds the scan set subject reference property
-            # # * adds the scan set key property
-            # # * adds each scan set registration's source reference property
-            # extendScanSets = ->
-            #   # Extends the modeling objects in the given modelable as follows:
-            #   # * adds the modeling key property
-            #   # * adds the modeling source reference property
-            #   # * adds the modeling results session reference properties
-            #   #
-            #   # @param modelable the scan set or registration configuration
-            #   # @returns the modeling results
-            #   extendModelable = (modelable) ->
-            #     extendModeling = (modeling, key) ->
-            #       modeling.key = key
-            #       modeling.source = modelable
-            #       # Add the modeling results session reference.
-            #       for mdlResult, i in modeling.results
-            #         mdlResult.session = subject.sessions[i]
-            #
-            #     # Extend the modeling objects.
-            #     if modelable.modeling?
-            #       for mdlKey, mdl of modelable.modeling
-            #         extendModeling(mdl, mdlKey)
-            #
-            #   extendScanSet = (scanSet, scanType) ->
-            #     # Set the subject reference.
-            #     scanSet.subject = subject
-            #     # Set the scan type.
-            #     scanSet.scanType = scanType
-            #     # Extend the modeling objects.
-            #     extendModelable(scanSet)
-            #     # Extend the registration configurations
-            #     for regKey, regCfg of scanSet.registration
-            #       # Set the registration key.
-            #       regCfg.key = regKey
-            #       # Set the registration source.
-            #       regCfg.source = scanSet
-            #       # Extend the registration modeling objects.
-            #       extendModelable(regCfg)
-            #
-            #   # Extend each scan set.
-            #   for scanType, scanSet of subject.scanSets
-            #     extendScanSet(scanSet, scanType)
-            #
             # Adds the modeling property to the fetched subject.
             addModeling = ->
-              # Adds the subject modeling property.
-              subjectModeling = ->
-                # @returns the associative session {source: modeling}
-                #   objects list
-                sourceModeling = (session) ->
-                  # @returns whether the modeling source is a scan
-                  isScanModeling = (modeling) ->
-                    modeling.source.scan?
+              # @returns the subject [{source, results}] array, where
+              #   each source value is a {source type: source id}
+              #   object and the results are the modeling results in
+              #   session number order.
+              subjectModelings = ->
+                # @param session the session object
+                # @returns the session {source type: {source id: result}}
+                #   object
+                associate = (accum, session) ->
+                  collect = (accum, modeling) ->
+                    assocPcl = accum[modeling.protocol]
+                    if not assocPcl?
+                      assocPcl = accum[modeling.protocol] = {}
+                    pairs = _.pairs(modeling.source)
+                    if pairs.length > 1
+                      throw ReferenceError("Modeling source cannot reference" +
+                                           "more than one source type:" +
+                                           " #{ Object.keys(modeling.source) }")
+                    [srcType, srcId] = pairs[0]
+                    assocSrc = assocPcl[srcType]
+                    if not assocSrc?
+                      assocSrc = assocPcl[srcType] = {}
+                    results = assocSrc[srcId]
+                    if not results?
+                      results = assocSrc[srcId] = []
+                    results[session.number - 1] = modeling.result
+                    accum

-                  # @returns the {source: modeling} associative object
-                  associateSource = (modeling) ->
-                    obj = {}
-                    obj[modeling.source] = modeling
-                    obj
+                  session.modelings.reduce(collect, accum)
+
+                create = (protocolId, sourceType, sourceId, results) ->
+                  protocol: protocolId
+                  source: ObjectHelper.associate(sourceType, sourceId)
+                  results: results
+
+                flattenBySource = (protocolId, srcAssoc) ->
+                  flattenBySourceId = (srcType) ->
+                    sortCriterion = (modeling) ->
+                      modeling.source[srcType]
+
+                    modelings = []
+                    # Make the [{protocol, source, results}] array from
+                    # the {source type: {source id: results}} object.
+                    for srcId, results of srcAssoc[srcType]
+                      obj = create(protocolId, srcType, srcId, results)
+                      modelings.push(obj)
+                    # Return the modeling objects sorted by source id.
+                    _.sortBy(modelings, sortCriterion)

-                  # Separate the scan modelings from the registration
-                  # modelings.
-                  [scanMdl, regMdl] = _.partition(session.modelings, isScanModeling)
-
-                  # Return the associative {scan, registration} object
-                  scan: [associateSource(modeling) for modeling in scanMdl]
-                  registration: [associateSource(modeling) for modeling in regMdl]
+                  srcTypes = ['scan', 'registration']
+                  modelingArrays = (
+                    flattenBySourceId(srcType) for srcType in srcTypes
+                  )
+                  _.flatten(modelingArrays)

-                # @returns the destination array with the zero-based
-                #   session index value set to the given modeling object
-                mergeIntoSessionArray = (destArray, modeling) ->
-                  if not destArray?
-                    destArray = []
-                  destArray[modeling.session.number - 1] = modeling
-                  destArray
-
-                # @param accumAssoc the accumulator scan or registration
-                #   {source: [modeling]} object, where the modeling array
-                #   is in session index order
-                # @param sessAssocArray the corresponding session scan
-                #   or registration [{source: modeling}] array
-                # @returns the combined {source: [modeling]} object
-                mergeModeling = (accumAssoc, sessAssocArray) ->
-                  _.merge(accumAssoc, sessAssocArray..., mergeIntoSessionArray)
-
-                # @returns a {scan: map, registration: map} object where each
-                #   map {source: [modeling]} associates a modeling source
-                #   with the session modeling objects for that source
-                associateModeling = (accum, session) ->
-                  # The {scan: [assoc], registration: [assoc]} object, where
-                  # each assoc is a {source: modeling} object.
-                  sessModeling = sourceModeling(session)
-                  # Return the {scan: map, registration: map} object, where
-                  # the map merges the modeling by source.
-                  _.merge(accum, sessModeling, mergeModeling)
-
-                # Return the associative accumulator object.
-                accum = {scan: {}, registration: {}}
-                subject.sessions.reduce(associateModeling, accum)
-
-              # Add the subject modeling property.
-              Object.defineProperty subject, 'modeling',
+                # Make the {protocol id: {source type: {source id: results}}}
+                # object.
+                assoc = subject.sessions.reduce(associate, {})
+                # Flatten into a [[{protocol, source, results}]]
+                # array of arrays partitioned by protocol id.
+                modelingArrays = (
+                  flattenBySource(pclId, srcAssoc) for pclId, srcAssoc of assoc
+                )
+                # Flatten into a [{protocol, source, results}] array.
+                _.flatten(modelingArrays)
+
+              # Add the subject modelings property.
+              Object.defineProperty subject, 'modelings',
                 enumerable: true
                 get: ->
-                  subjectModeling(subject)
+                  subjectModelings()

             # Fixes the subject date properties.
             fixDates = ->
@@ -173,20 +144,96 @@
                 trt.beginDate = DateHelper.asMoment(trt.beginDate)
                 trt.endDate = DateHelper.asMoment(trt.endDate)

-            # Makes the following changes to the subject session objects:
-            # * adds the session subject reference
-            # * fixes the session acquisition date
-            # * adds the session parent property to the session
-            #   modeling objects
+            # Makes the changes to the subject session objects
+            # described in the extendModeling and extendSession
+            # functions below.
             extendSessions = ->
-              for sess in subject.sessions
+              # Adds the following modeling parameter result
+              # properties:
+              # * key - the parameter result access property name
+              # * modelingResult - the parent modeling result
+              #   reference
+              # * overlay - the label map, if it exists and has a
+              #   color table
+              #
+              # If there is a label map, then this function sets the
+              # label map parent modeling parameter reference.
+              #
+              # @param paramResult the object to extend
+              # @param modelingResult the parent modeling result object
+              # @param key the parameter result access property name
+              extendParameterResult = (paramResult, modelingResult, key) ->
+                # The parameter key property identifies the
+                # parameter, e.g. ktrans.
+                paramResult.key = key
+                # Set the parent modeling result reference.
+                paramResult.modelingResult = modelingResult
+                if paramResult.labelMap?
+                  # Set the label map parent reference.
+                  paramResult.labelMap.parameterResult = paramResult
+                  # If the label map has a color table, then set
+                  # the overlay property.
+                  if paramResult.labelMap.colorTable?
+                    Object.defineProperty paramResult, 'overlay',
+                      get: ->
+                        @labelMap
+
+              # Adds the modeling result parent modeling object
+              # reference property and extends the parameter result
+              # objects as described in extendParameterResult.
+              #
+              # @param modelingResult the modeling result object to extend
+              # @param modeling the parent modeling object
+              extendModelingResult = (modelingResult, modeling) ->
+                # Set the modeling result parent reference.
+                modelingResult.modeling = modeling
+                # Extend each modeling parameter result object.
+                for key, paramResult of modelingResult
+                  extendParameterResult(paramResult, modelingResult, key)
+
+              # Extends the modeling result as described in
+              # extendModelingResult and adds the following
+              # modeling object properties:
+              # * session - the parent session object reference
+              # * overlays - the modeling result overlays
+              #
+              # @param modeling the modeling object to extend
+              # @param session the parent session object
+              extendModeling = (modeling, session) ->
+                # Set the modeling parent session reference.
+                modeling.session = session
+                # Extend the modeling result object.
+                extendModelingResult(modeling.result, modeling)
+                # The modeling overlays property returns the label maps
+                # which have a color table.
+                Object.defineProperty modeling, 'overlays',
+                  get: ->
+                    (res.overlay for res in _.values(@result) when res.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
+              extendSession = (session, number) ->
                 # Set the session subject property.
-                sess.subject = subject
+                session.subject = subject
+                # Set the session number property.
+                session.number = number
                 # Fix the acquisition date.
-                sess.acquisitionDate = DateHelper.asMoment(sess.acquisitionDate)
-                # Add the session parent property to the modeling objects.
-                for modeling in sess.modelings
-                  modeling.session = sess
+                session.acquisitionDate = DateHelper.asMoment(session.acquisitionDate)
+                # 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.
+                Object.defineProperty session, 'overlays',
+                  get: ->
+                    _.flatten(_.pluck(@modelings, 'overlays'))
+
+              # Extend each session.
+              for session, i in subject.sessions
+                extendSession(session, i+1)

             # Fix the subject dates.
             fixDates()
@@ -196,10 +243,9 @@
             addModeling()
             # Add the isMultiSession property.
             Object.defineProperty subject, 'isMultiSession',
-              enumerable: true
               # @returns whether there is more than one session
               get: ->
-                isMultiSession = subject.sessions.length > 1
+                isMultiSession = this.sessions.length > 1

             # End of extendSubject.

@@ -221,18 +267,19 @@
             else
               criteria = _.pick(condition, SUBJECT_SECONDARY_KEY_FIELDS)
               if not _.all(_.values(criteria))
-                throw new ValueError("The subject search condition is missing" +
-                                     " both an id value and a complete" +
-                                     " secondary key: #{ condition }")
+                throw new ValueError("The subject search condition is" +
+                                     " missing both an id value and a" +
+                                     " complete secondary key:" +
+                                     " #{ condition }")
               select = REST.where(criteria)
               Subject.query(select).$promise.then (subjects) ->
                 if not subjects.length
                   throw new ReferenceError("Subject was not found:" +
                                            " { _.pairs(template) }")
                 else if subjects.length > 1
-                  throw new ReferenceError("Subject query on the secondary key" +
-                                           " returned more than one subject:" +
-                                           " #{ _.pairs(template) }")
+                  throw new ReferenceError("Subject query on the secondary" +
+                                           " key returned more than one" +
+                                           " subject: #{ _.pairs(template) }")
                 # The unique subject that matches the query condition.
                 subjects[0]

@@ -265,118 +312,149 @@
         # @param session the parent object
         # @returns a promise which resolves to the session detail
         getSessionDetail: (session) ->
-          extendScan = (scan, scanType) ->
-            # Adds the session, container type and images properties to
-            # the given image container.
-            #
-            # @param container the image container
-            # @param key the scan type or registration name
-            addImageContainerContent = (container, key) ->
-              # The unique container id for caching.
-              container.id = "#{ session.detail }.#{ container._cls }.#{ key }"
-              # Encapsulate the image files.
-              container.images = Image.imagesFor(container)
-
-            # Adds the scan, key, configuration and image content
-            # properties to the given registration.
-            #
-            # @param registration the registration to extend
-            # @param scan the source scan
-            # @param key the registration key
-            extendRegistration = (registration, key, scan) ->
-              # Set the source scan reference.
-              registration.scan = scan
-              # Set the key.
-              registration.key = key
-              # The scan set.
-              scanSet = session.subject.scanSets[scan.scanType]
-              if not scanSet?
-                throw new ReferenceError "#{ subjectTitle(session) } does not have" +
-                                         " a #{ scan.scanType } scan set."
-              # Set the registration configuration.
-              registration.configuration = scanSet.registration[key]
-              if not registration.configuration?
-                throw new ReferenceError "#{ subjectTitle(session) } does not have" +
-                                         " a #{ key } registration configuration."
-
-              # Define the session property required by the image factory.
-              # Image containers present a uniform interface, which includes
-              # a session reference.
-              Object.defineProperty registration, 'session',
-                get: ->
-                  registration.scan.session
-              # Add the registration images.
-              addImageContainerContent(registration, key)
-
+          # Adds the following properties to the given volume object:
+          # * container - the volume parent scan or registration
+          # * image - the volume image object
+          #
+          # The image property creates and caches an image object
+          # on demand when it is first accessed.
+          #
+          # @param volume the volume object to extend
+          # @param number the one-based volume number
+          # @param container the image scan or registration object
+          extendVolume = (volume, number, container) ->
+            # The parent reference.
+            if container._cls is 'Scan'
+              volume.scan = container
+            else if container._cls is 'Registration'
+              volume.registration = container
+            else
+              throw TypeError("The image container type is not recognized")
+            # The container alias.
+            Object.defineProperty volume, 'container',
+              get: ->
+                @scan or @registration
+            # Set the volume number property.
+            volume.number = number
+            # The volume image object is created and cached on demand.
+            Object.defineProperty volume, 'image',
+              get: ->
+                if not @_image?
+                  @_image = Image.forVolume(volume)
+                @_image
+
+          # Extends the container volumes as described in extendVolume
+          # and adds the following properties to the given container:
+          # * session - the container parent session reference
+          #
+          # @param container the image scan or registration object
+          extendImageContainer = (container) ->
+            # Set the session property required by the image factory.
+            # Image containers present a uniform interface, which
+            # includes a session reference.
+            container.session = session
+            # Add the volume images.
+            for volume, index in container.volumes
+              extendVolume(volume, index + 1, container)
+
+          # Extends the given registration object as described in
+          # extendImageContainer and adds the following properties:
+          # * scan - the registration source scan
+          #
+          # @param registration the registration to extend
+          # @param scan the source scan
+          extendRegistration = (registration, scan) ->
+            # Set the source scan reference.
+            registration.scan = scan
+            # 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
+          # * 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.
+            # Convert it to an integer.
+            scan.number = parseInt(scan.number)
             # Set the session reference.
             scan.session = session
-            # Set the scan type property.
-            scan.scanType = scanType
-            # Add the images.
-            addImageContainerContent(scan, scanType)
-            # Add registration properties.
-            for key, reg of scan.registration
-              extendRegistration(reg, key, scan)
-
+            # Add the scan volume properties.
+            extendImageContainer(scan)
+            # Add the scan registration properties.
+            for reg in scan.registrations
+              extendRegistration(reg, scan)
+
+          # The session object must have a detail reference.
           if not session.detail?
             throw new ReferenceError "Subject #{ session.subject.number }" +
                                      " Session #{ session.number }" +
                                      " does not reference a detail object"
-
+          # Fetch the session detail.
           Session.detail(id: session.detail).$promise.then (detail) ->
             # Copy the fetched detail into the session.
             ObjectHelper.aliasPublicDataProperties(detail, session)
             # Add properties to the scans and their registration.
-            for key, scan of detail.scans
-              extendScan(scan, key)
+            for scan in session.scans
+              # Add properties.
+              extendScan(scan)
             # Resolve to the augmented session object.
             session

         # @param session the session to search
-        # @param scanType the scan type, t1 or t2
-        # @returnsa the scan object or a promise which resolves
-        #   to the scan object
+        # @param scan the scan number
+        # @returns the scan object
         # @throws ReferenceError if the session does not have the scan
-        getScan: (session, scanType) ->
-          scan = session.scans[scanType]
+        getScan: (session, scanNumber) ->
+          # The matching scan (there is at most one).
+          scan = _.find(session.scans,
+                        (scan) -> scan.number is scanNumber)
           # If no such scan, then complain.
           if not scan?
-            throw new ReferenceError "#{ subjectTitle(session) }" +
-                                     " does not have a #{ scanType } 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.
           scan

         # @param scan the scan to search
-        # @param key the registration key
-        # @returnsa the registration object or a promise which resolves
-        #   to the registration object
+        # @param resource the registration resource name
+        # @returns the registration object
         # @throws ReferenceError if the scan does not have the registration
-        getRegistration: (scan, key) ->
-          reg = scan.registration[key]
+        getRegistration: (scan, resource) ->
+          # The matching scan (there is at most one).
+          reg = _.find(scan.registrations,
+                       (reg) -> reg.resource is resource)
           # If no such registration, then complain.
           if not reg?
             throw new ReferenceError "#{ subjectTitle(session) }" +
-                                     " #{ scan.scanType } scan does not have" +
-                                     " a #{ key } registration."
+                                     " Scan #{ scan.number } does not have" +
+                                     " a registration with resource" +
+                                     " #{ resource }"
           # Return the registration.
           reg

         # @param container the image parent container object
-        # @param timePoint the one-based series time point
+        # @param number the one-based volume number
         # @returns the image object, if its data is loaded,
         #   otherwise a promise which resolves to the image object
-        #   when the image content is loaded
+        #   after the image content is loaded
         # @throws ReferenceError if the parent container does not have
         #   the image
-        getImageDetail: (container, timePoint) ->
+        getVolume: (container, number) ->
           # timePoint is one-based.
-          image = container.images[timePoint - 1]
+          image = container.images[number - 1]
           if not image?
             throw new ReferenceError "Subject #{ subject.number }" +
                                      " Session #{ session.number }" +
-                                     " does not have an image at" +
-                                     " #{ container.title } time point" +
-                                     " #{ timePoint }"
+                                     " #{ container.title } Volume" +
+                                     " #{ number } was not found"
           # If the image has data, then return the image.
           # Otherwise, return a promise to load the image.
           if image.data?


Hash prefix is unnecessary.

Hash prefix is unnecessary.

--- app/javascripts/routes.coffee
+++ app/javascripts/routes.coffee
@@ -58,7 +58,7 @@
                 project: $stateParams.project or project
                 collection: _s.capitalize($stateParams.collection)
                 number: parseInt($stateParams.subject)
-
+
           # The subject detail page.
           .state 'quip.subject.detail',
             url: '?detail'
@@ -131,5 +131,5 @@
           [path, paramStr].join('?')

         # HTML5 mode enables the back button to work properly.
-        $locationProvider.html5Mode(true).hashPrefix('!')
+        $locationProvider.html5Mode(true)
     ]


remove comments

remove comments

--- app/models/group-model.coffee
+++ app/models/group-model.coffee
@@ -25,8 +25,4 @@

     membershipFor: (member) ->
       _.first _.filter @memberships(), (membership) ->
-        membership.memberId == member.id
-
-    # personalFunds: ->
-
-    # totalFunds: ->+        membership.memberId == member.id


Fix footer publications link

Fix footer publications link

--- app/partials/main-footer.cjsx
+++ app/partials/main-footer.cjsx
@@ -115,7 +115,7 @@
             {@loggableLink <Link to="/about" />, 'footer.about.aboutUs'}
             {@loggableLink <Link to="/about/education" />, 'footer.about.education'}
             {@loggableLink <Link to="/about/team" />, 'footer.about.ourTeam'}
-            {@loggableLink <Link to="/about/education" />, 'footer.about.education'}
+            {@loggableLink <Link to="/about/publications" />, 'footer.about.publications'}
             {@loggableLink <Link to="/about/contact" />, 'footer.boilerplate.contact'}
           </div>
           <div className="site-map-section">


add product description to `enone` closes #100

--- assets/coffeescript/product_list.coffee
+++ assets/coffeescript/product_list.coffee
@@ -44,6 +44,7 @@
       id: 'enone'
       title_store: "none"
       title_cart: "Eye camera: no eye camera"
+      description_store: "No eye camera"
       weight: 26
     e30:
       id: 'e30'


Correctly handle null properties of resources even where the property uses a class.

--- coffee/classes/081_base_resource.tail.coffee
+++ coffee/classes/081_base_resource.tail.coffee
@@ -162,7 +162,7 @@
 				if isPropertyArray or jsonAggregateResources.length > 1
 					jsonExpressionSetter json, jsonAggregateResources
 				else
-					jsonExpressionSetter json, jsonAggregateResources[ 0 ] if jsonAggregateResources[ 0 ]?
+					jsonExpressionSetter json, jsonAggregateResources[ 0 ]

 		angoolar.delete json, 'this' if @$_useThis

@@ -207,7 +207,7 @@
 			if isPropertyArray or jsonResources.length > 1
 				propertyExpressionSetter @, jsonResources
 			else
-				propertyExpressionSetter @, jsonResources[ 0 ] if jsonResources[ 0 ]?
+				propertyExpressionSetter @, jsonResources[ 0 ]

 		angoolar.delete json, 'this' if @$_useThis


fixed bug that new group activities claim a non-group name/slug. added prefix of group

--- workers/social/lib/social/models/messages/post.coffee
+++ workers/social/lib/social/models/messages/post.coffee
@@ -45,7 +45,10 @@
   # TODO: these relationships may not be abstract enough to belong to JPost.
   @set
     slugifyFrom : 'title'
-    slugTemplate: 'Activity/#{slug}'
+    slugTemplate: ->
+      """
+      #{if @group is 'koding' then '' else "#{@group}/"}Activity/\#{slug}
+      """
     indexes     :
       slug      : 'unique'
       group     : 'sparse'


Rewards: added defensive checks

Rewards: added defensive checks

--- workers/social/lib/social/models/rewards/index.coffee
+++ workers/social/lib/social/models/rewards/index.coffee
@@ -159,7 +159,9 @@
     unless username
       return callback new KodingError "Please set username"

-    providedBy = client.connection.delegate.getId()
+    providedBy = client?.connection?.delegate?.getId()
+
+    return callback { message : "account is not set" }  if not providedBy

     JAccount.one 'profile.nickname': username, (err, account)->

@@ -203,17 +205,23 @@

   @fetchEarnedAmount$ = secure (client, options, callback)->

-    options.originId  = client.connection.delegate.getId()
+    providedBy = client?.connection?.delegate?.getId()
+
+    return callback { message : "account is not set" }  if not providedBy

     @fetchEarnedAmount options, callback

   @some$ = secure (client, selector, options, callback)->
+
+    originId = client?.connection?.delegate?.getId()
+
+    return callback { message : "account is not set" }  if not originId

     selector ?= {}
     options  ?= {}

-    selector.originId = client.connection.delegate.getId()
+    selector.originId = originId

     @some selector, options, callback


Inline code can be represented in a list.

This should fix #149. The only change needed was to reorder the parsing
of the document.

--- lib/ecrire/app/assets/javascripts/admin/editor/content.coffee
+++ lib/ecrire/app/assets/javascripts/admin/editor/content.coffee
@@ -366,8 +366,8 @@

   sort: =>
     [
+      @store.lists
       @store.code
-      @store.lists
       @store.image
       @store.headers
       @store.link


config: Add comments

config: Add comments

--- config.coffee
+++ config.coffee
@@ -49,11 +49,14 @@
   videoRTPServerPort : 7044  # even
   videoRTCPServerPort: 7045  # odd and contiguous

+  # Application name for live streams. Live streams will be accessible at
+  # rtsp://{host}:{serverPort}/{liveApplicationName}/{streamName} or
+  # rtmp://{host}:{rtmpServerPort}/{liveApplicationName}/{streamName}
   liveApplicationName: 'live'

   # MP4 files in recordedDir will be accessible at
-  # rtsp://{host}/{recordedApplicationName}/{filename} or
-  # rtmp://{host}/{recordedApplicationName}/mp4:{filename}
+  # rtsp://{host}:{serverPort}/{recordedApplicationName}/{filename} or
+  # rtmp://{host}:{rtmpServerPort}/{recordedApplicationName}/mp4:{filename}
   # To disable this feature, comment out the following two lines.
   recordedApplicationName: 'file'
   recordedDir: 'file'


skip buttons_box and content when not set

--- bokehjs/src/coffee/widget/dialog.coffee
+++ bokehjs/src/coffee/widget/dialog.coffee
@@ -26,9 +26,10 @@

     content = @mget('content')
     if typeof content is 'object'
-      @content_view = new content.default_view(model: content)
-      @$el.find('.bk-dialog-content').empty()
-      @$el.find('.bk-dialog-content').append(@content_view.$el)
+      if content?
+        @content_view = new content.default_view(model: content)
+        @$el.find('.bk-dialog-content').empty()
+        @$el.find('.bk-dialog-content').append(@content_view.$el)
     else
       @$el.find('.bk-dialog-content').empty()
       @$el.find('.bk-dialog-content').text(content)
@@ -38,9 +39,10 @@
     if @buttons_box_view?
       @buttons_box_view.remove()
     buttons_box = @mget('buttons_box')
-    @buttons_box_view = new buttons_box.default_view(model: buttons_box)
-    @$el.find('.bk-dialog-buttons_box').empty()
-    @$el.find('.bk-dialog-buttons_box').append(@buttons_box_view.$el)
+    if buttons_box?
+      @buttons_box_view = new buttons_box.default_view(model: buttons_box)
+      @$el.find('.bk-dialog-buttons_box').empty()
+      @$el.find('.bk-dialog-buttons_box').append(@buttons_box_view.$el)
     return @

   render: () ->


Adds a session cookie for denoting auth modal dismissal

--- apps/home/client/index.coffee
+++ apps/home/client/index.coffee
@@ -13,7 +13,7 @@
 featuredShowsTemplate = -> require('../templates/featured_shows.jade') arguments...
 featuredPostsTemplate = -> require('../templates/featured_posts.jade') arguments...
 featuredArtistsTemplate = -> require('../templates/featured_artists.jade') arguments...
-{ readCookie } = require '../../../components/util/cookie.coffee'
+{ readCookie, createCookie } = require '../../../components/util/cookie.coffee'
 user = null

 module.exports.init = ->
@@ -44,7 +44,11 @@
       $('#home-suggested-artworks').html featuredArtworksTemplate(artworks: artworks.models[0..3])

   # Open the signup modal for logged out users, or the login modal if the user has
-  # signed in before.
-  return if user? or location.search.match('no-auth-modal')
+  # signed in before, or if they have already dismissed it this session
+  return if user? or location.search.match('no-auth-modal') or readCookie('dismissed_auth_modal')
+
   mediator.trigger 'open:auth',
-    mode: if readCookie('signed_in') is 'true' then 'login' else 'signup'+    mode: if readCookie('signed_in') is 'true' then 'login' else 'signup'
+
+  mediator.on 'modal:closed', ->
+    createCookie 'dismissed_auth_modal', true


reactivity: chat input: remove emoji stores from common getters

--- client/activity/lib/flux/getters.coffee
+++ client/activity/lib/flux/getters.coffee
@@ -31,11 +31,6 @@
 UsersStore                     = [['UsersStore'], withEmptyMap]
 MessageLikersStore             = [['MessageLikersStore'], withEmptyMap]

-EmojisStore                         = [['EmojisStore'], withEmptyList]
-FilteredEmojiListQueryStore         = ['FilteredEmojiListQueryStore']
-FilteredEmojiListSelectedIndexStore = ['FilteredEmojiListSelectedIndexStore']
-CommonEmojiListSelectedIndexStore   = ['CommonEmojiListSelectedIndexStore']
-CommonEmojiListVisibilityStore      = ['CommonEmojiListVisibilityStore']
 ChatInputChannelsQueryStore         = ['ChatInputChannelsQueryStore']
 ChatInputChannelsSelectedIndexStore = ['ChatInputChannelsSelectedIndexStore']
 ChatInputChannelsVisibilityStore    = ['ChatInputChannelsVisibilityStore']


IDEView: Create drawing board with hash.

--- client/ide/views/tabview/ideview.coffee
+++ client/ide/views/tabview/ideview.coffee
@@ -140,8 +140,8 @@
     @emit 'ChangeHappened', change

-  createDrawingBoard: ->
-    drawingPane = new IDE.DrawingPane
+  createDrawingBoard: (paneHash) ->
+    drawingPane = new IDE.DrawingPane { hash: paneHash }
     @createPane_ drawingPane, { name: 'Drawing' }
     @emitChange  drawingPane, context: {}


Fix failing tests for h2o-proxy.

Fix failing tests for h2o-proxy.

--- client/src/tests/h2o-proxy.tests.coffee
+++ client/src/tests/h2o-proxy.tests.coffee
@@ -94,7 +94,7 @@
   _ = Steam.ApplicationContext()
   Steam.Xhr _
   Steam.H2OProxy _
-  t.plan 25
+  t.plan 23
   _.requestFramesAndCompatibleModels (error, result) ->
     typecheckFramesResult t, error, result
     { frames } = result
@@ -106,7 +106,7 @@
   _ = Steam.ApplicationContext()
   Steam.Xhr _
   Steam.H2OProxy _
-  t.plan 16
+  t.plan 15
   _.requestFrameAndCompatibleModels 'airlines_train.hex', (error, result) ->
     typecheckFramesResult t, error, result
     { response, frames } = result
@@ -151,7 +151,7 @@
   _ = Steam.ApplicationContext()
   Steam.Xhr _
   Steam.H2OProxy _
-  t.plan 25
+  t.plan 23
   _.requestModelsAndCompatibleFrames (error, result) ->
     typecheckModelsResult t, error, result
     { models } = result


added url identifier

added url identifier

--- assets/js/IdeaListView.coffee
+++ assets/js/IdeaListView.coffee
@@ -16,6 +16,7 @@
     defaults:
       listType: 'unspecified'
       body: ''
+      urlsReplaced: false

     setListType: (listType) ->
       if listType in @_listTypeEnum
@@ -69,14 +70,25 @@

     initialize: ->
       _.bindAll @, 'render'
+      @regex = new RegExp /\b(?:https?|ftp):\/\/[a-z0-9-+&@#\/%?=~_|!:,.;]*[a-z0-9-+&@#\/%=~_|]/gim
+      @pseudoRegex = new RegExp /(^|[^\/])(www\.[\S]+(\b|$))/gim

     render: ->
-      if moment(@model.get('datetime')).isValid()
-        @model.set "datetime", new moment(@model.get('datetime')).calendar()
+      @gentrifyModel()
       $(@el).html "
#{@model.get 'datetime'}" +
                   "#{@model.get 'listType'}
" +
                   "
#{@model.get 'body'}
"
       @
+
+    gentrifyModel: ->
+      if moment(@model.get('datetime')).isValid()
+        @model.set 'datetime', new moment(@model.get('datetime')).calendar()
+      if not @model.get 'urlsReplaced'
+        @model.set('body', @model.get('body')
+          .replace(@regex, '$&')
+          .replace @pseudoRegex, '$1$2')
+        console.log @pseudoRegex
+        @model.set 'urlsReplaced', true

   class IdeaListView extends Backbone.View
@@ -122,7 +134,6 @@

     filterByBody: (evt) ->
       filteredList = @collection.filterByBody evt.target.value
-      console.log filteredList
       $('.idea-item').addClass 'hidden'
       for item in filteredList.models
         @appendItem item


fix whitespace

fix whitespace

--- js/src/crypto/AesCryptoService.coffee
+++ js/src/crypto/AesCryptoService.coffee
@@ -58,7 +58,7 @@

       if not @key
         logger.info('no key passed! generating a key.')
-        @key = forge.random.getBytesSync(@cypher.keySize / BITS_PER_BYTE)
+        @key = forge.random.getBytesSync(@cypher.keySize / BITS_PER_BYTE)
       @abstractCryptoService = new AbstractCryptoService(@cypher)

     encrypt: (plaintext) ->


clean up event handlers from phyo placement

--- coffee/main.coffee
+++ coffee/main.coffee
@@ -1438,6 +1438,7 @@
       #     this way many things may be linked without going back to the icon
       if e instanceof Link
         console.log "! linking objects"
+        @mh.placingObject = null
         @mh.sv.container.onmousemove = (eve) => @mh.linkingH.handleMove0(eve)
         @mh.sv.container.onmousedown = (eve) => @mh.linkingH.handleDown0(eve)
       else
@@ -1541,6 +1542,8 @@

     @mh.sv.container.onmousemove = null
     @mh.sv.container.onmouseup = null
+    @mh.sv.ve.scontainer.onmouseup = (eve) => null
+    @mh.sv.container.onmousedown = (eve) => @mh.baseDown(eve)
     @instance = null

   handleMove0: (event) ->
@@ -1925,7 +1928,7 @@
       console.log "! link1 miss"

   handleMove0: (event) ->
-    @.mh.updateMouse(event)
+    @mh.updateMouse(event)
     #console.log "! lm0"

   handleMove1: (event) ->


audio tracks delete their loader after loading, so they can be cached and won't decode every time they are redrawn

--- src/prong/track/audio.coffee
+++ src/prong/track/audio.coffee
@@ -89,7 +89,7 @@
             # on progress as it goes. The default one loads from http. Once
             # the loader is complete, the track should have channel and
             # buffer properties
-            if not ('_loader' of d)
+            if not (d._loader)
                 if '_channel' of d
                     # if we already have a channel set, set a 'do nothing' loader
                     d._loader = (_,callback) -> callback()
@@ -111,6 +111,7 @@

             d._loader loadingMessage, ->
+                d._loader = null
                 loadingMessage.remove()
                 waveform = Waveform()
                     .x(x)
@@ -284,6 +285,4 @@
         return audio

-    return d3.rebind(audio, commonProperties(), 'sequence','height')
-
-
+    return d3.rebind(audio, commonProperties(), 'sequence','height')


Bug#162: ScrollTop on all url changes

Bug#162: ScrollTop on all url changes

--- app/coffee/app.coffee
+++ app/coffee/app.coffee
@@ -429,6 +429,10 @@
         }
         checksley.updateMessages('default', messages)

+    $rootScope.$on "$routeChangeSuccess", ->
+        $('html, body').scrollTop(0)
+
+
 angular.module('taiga', modules)
        .config(['$routeProvider', '$locationProvider', '$httpProvider', '$provide', '$compileProvider', '$gmUrlsProvider', configCallback])
        .run(['$rootScope', '$location', '$gmStorage', '$gmAuth', '$gmUrls', '$i18next', 'config', '$data', '$log', init])


pricing: remove continuation links in thank you view

--- client/Pricing/AppView.coffee
+++ client/Pricing/AppView.coffee
@@ -25,25 +25,10 @@
         }
         """

-    {tags} = @subscription.productData.plan
-    unless @subscription.createAccount or groupPlanType in tags
-      @thankYou.addSubView @getContinuationLinks()
-
     if "custom-plan" in @workflowData.productData.plan.tags
       @thankYou.addSubView @createGroupNameForm()

     @addSubView @thankYou
-
-  getContinuationLinks: ->
-    new KDCustomHTMLView partial:
-      """
-

-
Activity

-
Account

-
Subscriptions

-
Environments

-

-      """

   createGroupNameForm: ->
     @groupForm              = new KDFormViewWithFields


fix: load utils directory

fix: load utils directory

--- app/initialize.coffee
+++ app/initialize.coffee
@@ -5,8 +5,8 @@
 # Load all modules in order automagically. Ember likes things to work this
 # way so everything is in the App.* namespace.
 folderOrder = [
-  'initializers', 'mixins', 'routes', 'models', 'views', 'controllers',
-  'helpers', 'templates', 'components'
+  'initializers', 'utils', 'mixins', 'routes', 'models', 'views',
+  'controllers', 'helpers', 'templates', 'components'
 ]

 folderOrder.forEach (folder) ->


Add a test to unsure appending extract strings

--- test/alias_test.coffee
+++ test/alias_test.coffee
@@ -79,6 +79,8 @@
       sharedExample done, 'hubot foo', 'goo'
     it 'replaces alias string with params', (done)->
       sharedExample done, 'hubot params john hello', 'goo --name=john --message=hello'
+    it 'appends unused string', (done)->
+      sharedExample done, 'hubot params john hello hey', 'goo --name=john --message=hello hey'
     it 'does not replace front-matching string', (done)->
       sharedExample done, 'hubot foos', 'foos'
     it 'does not replace anything', (done)->


do not show app tags twice

do not show app tags twice

--- client/views/apps/apps.coffee
+++ client/views/apps/apps.coffee
@@ -19,7 +19,7 @@
   searchTerms: -> Session.get('queryAppName')
   appDefTemplate: -> appDefTemplate
   hash: -> CryptoJS.MD5 "#{@name}#{@version}"
-  allTags: -> _.uniq(ApplicationDefs.find(name: @name).map (ad) -> ad.tags)
+  allTags: -> _.uniq _.flatten(ApplicationDefs.find(name: @name).map (ad) -> ad.tags)

 Template.apps.events
   'click .dropdown-menu': (e) -> e.stopPropagation() unless e.target.tagName.toUpperCase() == 'BUTTON'


Add test for unless-else branch

Add test for unless-else branch

--- test/conditions.coffee
+++ test/conditions.coffee
@@ -86,3 +86,6 @@

     it "negates 'true'", ->
       renderTest('','{% unless true %} FALSE {% endunless %}')
+
+    it "supports else", ->
+      renderTest(' TRUE ','{% unless true %} FALSE {% else %} TRUE {% endunless %}')


[TEST] Added some tests for conditions

[TEST] Added some tests for conditions

--- test/conditions.coffee
+++ test/conditions.coffee
@@ -1,6 +1,16 @@
 Promise = require "bluebird"

 describe "Liquid.Condition", ->
+
+  it "evaluates without a context", ->
+    c = new Liquid.Condition "1", "==", "1"
+
+    expect(c.evaluate()).to.be.fulfilled.then (v) ->
+      expect(v).to.equal true
+
+  it "fails on illegal operators", ->
+    renderTest("Liquid error: Unknown operator baz", "{% if foo baz bar %}X{% endif %}", {}, false)
+
   context "if", ->
     it "renders on `true` variables", ->
       renderTest('X', '{% if var %}X{% endif %}', var: true)


misc test cleanup

misc test cleanup

--- test/giphy.spec.coffee
+++ test/giphy.spec.coffee
@@ -483,7 +483,7 @@
         callback.should.not.have.been.called

     describe '.getUriFromResultData', ->
-      it 'returns .data.images.original.url', ->
+      it 'returns .images.original.url', ->
         uri = @giphy.getUriFromResultData sampleData
         should.exist uri
         uri.should.eql sampleData.images.original.url


failing tests for #148

failing tests for #148

--- test/specs/scan.coffee
+++ test/specs/scan.coffee
@@ -1,4 +1,5 @@
-{stream, prop, send, Kefir} = require('../test-helpers.coffee')
+{stream, prop, send, Kefir, activate, deactivate} = require('../test-helpers.coffee')
+sinon = require('sinon')

 noop = ->
 minus = (prev, next) -> prev - next
@@ -32,6 +33,25 @@
       a = stream()
       expect(a.scan minus).errorsToFlow(a)

+    it 'should never pass a value as current result if seed specified (test with error)', ->
+      a = stream()
+      handler = sinon.stub().returns('abc')
+      b = a.scan handler, 'seed'
+      activate(b)
+      send(a, [1, {error: 'err'}, 2, 3, '<end>'])
+      deactivate(b)
+      expect(handler.args.filter((xs) -> typeof xs[0] == 'number')).toEqual([])
+
+    it 'should fall back to the seed after error, if seed specified', ->
+      a = stream()
+      expect(a.scan (-> 'abc'), 'seed').toEmit [{current: 'seed'}, 'abc', {error: 'err'}, 'seed', 'abc', '<end>'], ->
+        send(a, [1, {error: 'err'}, 2, 3, '<end>'])
+
+    it 'should use next value after error as seed, if seed not specified', ->
+      a = stream()
+      expect(a.scan -> 'abc').toEmit [1, 'abc', {error: 'err'}, 3, 'abc', '<end>'], ->
+        send(a, [1, 2, {error: 'err'}, 3, 4, '<end>'])
+

   describe 'property', ->


Added tests for createPropertyMapper.

Added tests for createPropertyMapper.

--- test/utils-test.coffee
+++ test/utils-test.coffee
@@ -32,6 +32,23 @@
 			format = 'markdown'
 			text = 'Markdown *em*.'
 			expect(utils.createFormatMapper()(format, text)).toEqual text
+
+	describe 'createPropertyMapper', ->
+		it 'takes a property key and a function, when applied to an object it sets the object key to the function return value', ->
+			input =
+				foo: 'bar'
+			test =
+				foo: 'bar'
+				baz: 'quux'
+			fn = ->
+				'quux'
+			expect(utils.createPropertyMapper('baz', fn).call input).toEqual test
+		it 'returns null if the property key already exists', ->
+			input =
+				foo: 'bar'
+			fn = ->
+				'baz'
+			expect(utils.createPropertyMapper('foo', fn).call input).toBe null

 	describe 'getProps', ->
 		it 'returns an object with only the props in the provided array of keys', ->


simplified empty test logic

simplified empty test logic

--- testRequirement.coffee
+++ testRequirement.coffee
@@ -103,11 +103,8 @@
   if typeof shouldBeEmpty isnt 'boolean'
     return false

-  if shouldBeEmpty #return true if objectValue is empty
-    return isEmpty objectValue
-
-  else #return true if objectValue is not empty
-    return not isEmpty objectValue
+  empty = isEmpty objectValue
+  if shouldBeEmpty then empty else not empty

 # Check if a field value is empty (if it is "", [], {}, null, 0)
 isEmpty = (objectValue) ->


[grunt] more neutral argument name for Nunjucks global `getPage()` and updated docs

--- tasks/templates.coffee
+++ tasks/templates.coffee
@@ -199,13 +199,13 @@
             else return ctxValue

           ###*
-           * Get information about page and its childs from specified object.
-           * @param {array}  path                              Path to page inside `obj`
-           * @param {object} pages = localizedData.site.pages  Object with properties of page and its childs
-           * @return {object} Contains all page's properties, including it's sub pages
-          ###
-          env.addGlobal 'getPage', (path, pages = localizedData.site.pages) ->
-            result = _.get(pages, path)
+           * Get properties of page and its childs from specified object.
+           * @param {array}  path                             Path to page inside `data`
+           * @param {object} data = localizedData.site.pages  Object with properties of page and its childs
+           * @return {object} Properties of the page, including its sub pages
+          ###
+          env.addGlobal 'getPage', (path, data = localizedData.site.pages) ->
+            result = _.get(data, path)

             if result
               Object.defineProperty result, 'props', enumerable: false


added date to search results

added date to search results

--- _assets/js/_search.coffee
+++ _assets/js/_search.coffee
@@ -9,7 +9,7 @@
     searchInput: document.getElementById('search-posts')
     resultsContainer: document.getElementById('search-results')
     json: $baseUrl + '/search.json'
-    searchResultTemplate: '
{title}
'
+    searchResultTemplate: '
{title}
{date}

'
     noResultsText: '
No results found
'
     limit: 10
     fuzzy: false


Fixed some information of the site

Fixed some information of the site

--- docpad.coffee
+++ docpad.coffee
@@ -16,7 +16,7 @@

 			# Here are some old site urls that you would like to redirect from
 			oldUrls: [
-				'beta.hsmty.org'
+				#'beta.hsmty.org'
 			]

 			# The default title of our website


test for find precised

test for find precised

--- test/data_tests.coffee
+++ test/data_tests.coffee
@@ -53,10 +53,10 @@
     describe "Find", ->
         it "When I send a request to get Note with id 123", (done) ->
             client.get "data/123/", (error, response, body) =>
-                # TODO add catch of needed elements
+                @response = response

         it "Then error 404 should be returned", ->
-            # TODO add check http return value
+            @response.statusCode.should.be.equal.to(404)

         it "When I send a request to get Note with id 321", (done) ->
             client.get 'data/321/', (error, response, body) =>


Reword test contexts

Reword test contexts

--- test/elvis-test.coffee
+++ test/elvis-test.coffee
@@ -5,7 +5,7 @@
   event.initEvent(type, true, false)
   event

-describe 'el', ->
+describe 'elvis', ->
   describe 'can create', ->
     it 'div by default', ->
       expect(el().tagName).to.equal('DIV')
@@ -188,7 +188,7 @@
       expect(element.innerHTML).to.equal('foo')

-describe 'el.css', ->
+describe 'elvis.css', ->
   it 'can create basic css properties', ->
     css = el.css(color: '#00f')
     expect(css).to.equal('color:#00f;')
@@ -202,7 +202,7 @@
     expect(css).to.equal('body{padding:10px;}')

-describe 'el.setAttr', ->
+describe 'elvis.setAttr', ->
   it 'can set element id', ->
     element = el()
     el.setAttr(element, 'id', 'some-id')
@@ -250,7 +250,7 @@
     expect(element.href).to.match(new RegExp('/foo/bar'))

-describe 'el.getAttr', ->
+describe 'elvis.getAttr', ->
   it 'can get element id', ->
     expect(el.getAttr(el('#some-id'), 'id')).to.equal('some-id')

@@ -269,7 +269,7 @@
     expect(el.getAttr(element, 'text')).to.equal('foo bar')

-describe 'el.on', ->
+describe 'elvis.on', ->
   it 'can add event listeners', ->
     element = el('input')
     spy = sinon.spy()
@@ -278,7 +278,7 @@
     expect(spy).to.be.calledOnce

-describe 'el.text', ->
+describe 'elvis.text', ->
   it 'can create a text node', ->
     node = el.text('foo')
     expect(node.nodeType).to.equal(document.TEXT_NODE)


undoing errant regression tests because I forgot to branch for the pull request

--- test/forms_test.coffee
+++ test/forms_test.coffee
@@ -342,13 +342,6 @@
       it "should callback", ->
         browser.assert.elements "#field-uncheck:checked", 1

-    describe "inline callback", ->
-      before ->
-        browser.uncheck("You bet")
-
-      it "should invoke the callback", (done) ->
-        browser.check("You bet", done)
-
     describe "prevent default", ->
       values = []

@@ -473,13 +466,6 @@

       it "should fire blur event on previous field", ->
         assert true
-
-    describe "inline callback", ->
-      before ->
-        browser.choose("Not scary")
-
-      it "should invoke the callback", (done) ->
-        browser.choose("Scary", done)

   describe "select option", ->


Test force option

Test force option

--- test/middleware.coffee
+++ test/middleware.coffee
@@ -20,6 +20,30 @@
           should.not.exist err
           content.should.eql "(function() {\n\n  alert(\'welcome\');\n\n}).call(this);\n"
           next()
+
+  it 'should compile with force option', (next) ->
+    rimraf "#{__dirname}/../sample/public", (err) ->
+      options =
+        src: "#{__dirname}/../sample/view"
+        dest: "#{__dirname}/../sample/public"
+        force: true
+      req =
+        url: 'http://localhost/test.js'
+        method: 'GET'
+      res = {}
+      middleware(options) req, res, () ->
+        fs.stat "#{__dirname}/../sample/public/test.js", (err, stat) ->
+          mtime = stat.mtime
+          setTimeout ->
+            middleware(options) req, res, () ->
+              fs.stat "#{__dirname}/../sample/public/test.js", (err, stat) ->
+                # File should be modified
+                mtime.should.be.below stat.mtime
+                fs.readFile "#{__dirname}/../sample/public/test.js", 'utf8', (err, content) ->
+                  should.not.exist err
+                  content.should.eql "(function() {\n\n  alert(\'welcome\');\n\n}).call(this);\n"
+                  next()
+          , 500

   it 'should honor the base directory and bare option', (next) ->
     rimraf "#{__dirname}/../sample/public", (err) ->


Need to set new state if selectedUserId is null

null actually has a different meaning from undefined here.

--- resources/assets/coffee/react/beatmap-discussions/main.coffee
+++ resources/assets/coffee/react/beatmap-discussions/main.coffee
@@ -406,8 +406,7 @@
       else if @state.currentMode == 'events'
         newState.currentFilter = @lastFilter ? 'total'

-    if selectedUserId?
-      newState.selectedUserId = selectedUserId
+    newState.selectedUserId = selectedUserId if selectedUserId != undefined # need to setState if null

     @setState newState, callback


Sort non-react functions by name

Sort non-react functions by name

--- resources/assets/coffee/react/beatmap-discussions/post.coffee
+++ resources/assets/coffee/react/beatmap-discussions/post.coffee
@@ -112,38 +112,8 @@
         @messageEditor()

-  handleEnter: (e) =>
-    return if e.keyCode != 13 || e.shiftKey
-
-    e.preventDefault()
-    @throttledUpdatePost()
-
-
-  setMessage: (e) =>
-    @setState message: e.target.value
-
-
-  updatePost: =>
-    if @state.message == @props.post.message
-      @setState editing: false
-      return
-
-    @setState posting: true
-
-    @xhr.updatePost?.abort()
-    @xhr.updatePost = $.ajax laroute.route('beatmap-discussion-posts.update', beatmap_discussion_post: @props.post.id),
-      method: 'PUT'
-      data:
-        beatmap_discussion_post:
-          message: @state.message
-
-    .done (data) =>
-      @setState editing: false
-      $.publish 'beatmapsetDiscussion:update', beatmapsetDiscussion: data.beatmapset_discussion
-
-    .fail osu.ajaxError
-
-    .always => @setState posting: false
+  editEnd: =>
+    @setState editing: false

   editStart: =>
@@ -151,10 +121,6 @@

     @setState editing: true, =>
       @textarea.focus()
-
-
-  editEnd: =>
-    @setState editing: false

   formattedMessage: =>
@@ -170,8 +136,45 @@
     text

+  handleEnter: (e) =>
+    return if e.keyCode != 13 || e.shiftKey
+
+    e.preventDefault()
+    @throttledUpdatePost()
+
+
   isOwner: =>
     @props.post.user_id == @props.beatmapset.user_id
+
+
+  messageEditor: =>
+    return if !@props.canBeEdited
+
+    div className: "#{bn}__message-container #{'hidden' if !@state.editing}",
+      textarea
+        ref: (el) => @textarea = el
+        className: "#{bn}__message #{bn}__message--editor"
+        onChange: @setMessage
+        onKeyDown: @handleEnter
+        value: @state.message
+
+      div className: "#{bn}__actions",
+        div className: "#{bn}__actions-group"
+
+        div className: "#{bn}__actions-group",
+          div className: "#{bn}__action",
+            el BigButton,
+              text: osu.trans 'common.buttons.cancel'
+              props:
+                onClick: @editEnd
+                disabled: @state.posting
+
+          div className: "#{bn}__action",
+            el BigButton,
+              text: osu.trans 'common.buttons.save'
+              props:
+                onClick: @throttledUpdatePost
+                disabled: @state.posting

   messageViewer: =>
@@ -273,39 +276,35 @@
                 osu.trans('beatmaps.discussions.allow_kudosu')

-
-  messageEditor: =>
-    return if !@props.canBeEdited
-
-    div className: "#{bn}__message-container #{'hidden' if !@state.editing}",
-      textarea
-        ref: (el) => @textarea = el
-        className: "#{bn}__message #{bn}__message--editor"
-        onChange: @setMessage
-        onKeyDown: @handleEnter
-        value: @state.message
-
-      div className: "#{bn}__actions",
-        div className: "#{bn}__actions-group"
-
-        div className: "#{bn}__actions-group",
-          div className: "#{bn}__action",
-            el BigButton,
-              text: osu.trans 'common.buttons.cancel'
-              props:
-                onClick: @editEnd
-                disabled: @state.posting
-
-          div className: "#{bn}__action",
-            el BigButton,
-              text: osu.trans 'common.buttons.save'
-              props:
-                onClick: @throttledUpdatePost
-                disabled: @state.posting
-
-
   permalink: (e) =>
     e.preventDefault()
+
+
+  setMessage: (e) =>
+    @setState message: e.target.value
+
+
+  updatePost: =>
+    if @state.message == @props.post.message
+      @setState editing: false
+      return
+
+    @setState posting: true
+
+    @xhr.updatePost?.abort()
+    @xhr.updatePost = $.ajax laroute.route('beatmap-discussion-posts.update', beatmap_discussion_post: @props.post.id),
+      method: 'PUT'
+      data:
+        beatmap_discussion_post:
+          message: @state.message
+
+    .done (data) =>
+      @setState editing: false
+      $.publish 'beatmapsetDiscussion:update', beatmapsetDiscussion: data.beatmapset_discussion
+
+    .fail osu.ajaxError
+
+    .always => @setState posting: false

   userModerationGroup: =>


Disable journalling for mongod

Disable journalling for mongod

Signed-off-by: Sonmez Kartal <d2d15c3d37396a5f2a09f8b42cf5f27d43a3fbde@koding.com>

--- config/generateRunFile.coffee
+++ config/generateRunFile.coffee
@@ -387,7 +387,7 @@
     }

     function runMongoDocker () {
-        docker run -d -p 27017:27017 --name=mongo mongo:2.4
+        docker run -d -p 27017:27017 --name=mongo mongo:2.4 --nojournal
         check_connectivity mongo
     }


Builder: project relative paths fixed

Builder: project relative paths fixed

--- Builder.coffee
+++ Builder.coffee
@@ -447,7 +447,7 @@
   getProjects:->

     rp = (address)=>
-      address?.replace /^website\//, '' #@config.uri.address
+      address?.replace /^website\//, '/' #@config.uri.address

     apps = {}
     {projects, bundles} = require './projects'


remove console.log

remove console.log

--- tests/translate.coffee
+++ tests/translate.coffee
@@ -93,7 +93,6 @@
     try
       T = new Translate('blah')
     catch e
-      console.log(e)
       should.exist(e)

   describe 'each language', ->


some debug

some debug


ka_de_kas

--- test/ka_de_kas.coffee
+++ test/ka_de_kas.coffee
@@ -7,14 +7,12 @@
   Lib: new EMTLib

-# describe "Rules", ->
-#   describe rule.description, ->
-#     text = " Кое -как,  кой -кого,  все -таки "
-#     eqs = " Кое-как,  кой-кого,  все-таки "
+describe "Rules", ->
+  describe rule.description, ->
+    text = ' скажите ка, он де, ну кась '

+    it text, ->
+      rule.text = text
+      rule.apply()
+      assert.equal " скажите-ка, он-де, ну-кась ", rule.text

-#     it text, ->
-#       rule.text = text
-#       rule.apply()
-#       assert.equal " Кое-как,  кой-кого,  все-таки ", rule.text
-


made tests more clear

made tests more clear

--- test/quad_tree.coffee
+++ test/quad_tree.coffee
@@ -34,7 +34,7 @@

   ok quadTree.root().I.nodes.length

-test "points should end up in the correct nodes", ->
+test "should retrieve correct number of nodes", ->
   quadTree = QuadTree(
     maxChildren: 4
   )
@@ -43,8 +43,16 @@
     quadTree.insert
       x: 1 + n * 50
       y: 50
-
-  equals quadTree.root().I.nodes[0].I.nodes[0].I.children.length, 3
-  equals quadTree.root().I.nodes[0].I.nodes[1].I.children.length, 2
+
+  node1 = quadTree.retrieve
+    x: 0
+    y: 0
+
+  node2 = quadTree.retrieve
+    x: 150
+    y: 0
+
+  equals node1.length, 3
+  equals node2.length, 2

 module()


Split the difference on the interval time for homepage units.

--- apps/home/client/hero_unit_view.coffee
+++ apps/home/client/hero_unit_view.coffee
@@ -4,7 +4,7 @@
 mediator = require '../../../lib/mediator.coffee'

 module.exports = class HeroUnitView extends Backbone.View
-  pauseLength: 8000
+  pauseLength: 6500

   events:
     'click #home-hero-units-left-arrow': 'onLeftArrow'
@@ -19,7 +19,7 @@
     @$heroUnits = @$('.home-hero-unit')

     @setBodyClass()
-    @initInterval()
+    @setInterval()

     @$window.on 'scroll', _.throttle @setBodyClass, 100
     @$window.on 'keyup', (e) => @onKeyUp(e)
@@ -31,9 +31,6 @@
       $(this)
         .height($(this).height())
         .attr 'src', $(this).attr('data-retina')
-
-  initInterval: ->
-    if (@$heroUnits.first().data('type') is 'welcome') then @setInterval else _.delay(@setInterval, @pauseLength)

   setInterval: =>
     clearInterval @interval
@@ -86,6 +83,7 @@
     @nextHeroUnit()

   onDot: (e) ->
+    @setInterval()
     @showHeroUnit $(e.target).index()

   signUp: (e) ->


update submit event

update submit event


Update commands and help message

Update commands and help message

--- scripts/bungie.coffee
+++ scripts/bungie.coffee
@@ -32,22 +32,25 @@
       robot.emit('slack-attachment', payload)

     # activity should always be last input
-    inputFirst = input[0].toLowerCase()
-    activityKey = Constants.ACTIVITY_KEYS[inputFirst]
-    if activityKey
+    firstCommand = input[0].toLowerCase()
+    if 'activity'.startsWith firstCommand
+      if input.length < 2
+        return sendError(robot, res, 'Please specify the activity type.')
+
+      activityKey = Constants.ACTIVITY_KEYS[input[1]]
       getPublicWeeklyActivity(res, activityKey).then (activityDetails) ->
         sendMessage(robot, res, [dataHelper.parseActivityDetails(activityDetails)])
       , (err) ->
         sendError(robot, res, err)

-    else if inputFirst is 'vendor'
+    else if 'vendor'.startsWith firstCommand
       for vendorHash in Constants.VENDORS
         getVendorDetails(res, vendorHash).then (vendorDetails) ->
           return
         , (err) ->
           sendError(robot, res, err)

-    else if inputFirst is 'bounties'
+    else if 'bounties'.startsWith firstCommand
       if input.length < 2
         return sendError(robot, res, 'Please specify which vendor you\'re looking up bounties for.')

@@ -88,8 +91,17 @@
     else
       # command not recognized. Lists all available commands
       message = "Available commands are:\n"
+
+      # activity
+      message += '`activity` +\n'
       for command in Constants.COMMANDS
         message += "`#{command}`\n"
+
+      # bounties
+      message += '`bounties` +\n'
+      for command in Object.keys Constants.BOUNTY_VENDORS
+        message += "`#{command}`\n"
+
       message += "\n#{helpText}"
       return sendError(robot, res, message)

@@ -173,6 +185,9 @@

 # Sends error message as DM in slack
 sendError = (robot, res, message) ->
+
+  message += '\nUse the `help` command for available commands.'
+
   console.log 'Sending error message:'
   console.log message
   robot.send {room: res.message.user.name, "unfurl_media": false}, message